I have a code that uses router.navigate to forward the user to a place
abrirLista(categoria, id) {
localStorage.setItem('categName', JSON.stringify(categoria));
const slug = slugify(categoria);
this.router.navigate(['/lista', 'categoria', slug], { queryParams: { id } });
}
as you can see it saves data in localstorage
with this other code I retrieve the data in Angular's ngOnInit, and do the interpolation in my html
getCategName(){
if(localStorage.getItem('categName')) {
this.categName = JSON.parse(localStorage.getItem('categName'))
}
}
<li class="breadcrumb-item active" aria-current="page">{{categName}}</li>
the problem is that the abrirLista It's in a component in the header, so it doesn't update the page and doesn't enter ngOnInit to execute the function getCategName, so it can't update the variable this.categName
I made a button that updates, but the idea is that whenever I execute the function abrirLista he updated the variable, but I'm not able to see how to do it
teste(){
this.categName = JSON.parse(localStorage.getItem('categName'))
}
So what you are trying to do by using this browser API (localStorage) is to store data such that you persist it for the second component to consume it. However you do not have the right connection established between the components so they can communicate the change and update your view. Angular has its own mechanism for such purposes. You can use:
Notifer and Listener (Observer pattern) with RxJS Subjects.
For this paradigm you need to use a service (Singelton pattern) class to setup the communication between the notifier and the listener components.
#Input and #Output property and event binding to share data between components without a service
Related
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 ;
}
In my angular project I currently have a service that uses http calls to retrieve data from my java code. Every 10 seconds the service calls the Java side and gets new data from it. I now have another component that needs the data in my service. I need this component to have a field called 'data' that just gets updated automatically when the service gets new information.
How can I set them up so that the service pushes the new information to my other component? I would like to be able to use {{data}} in my component's html and have that be automatically updated without having to reload the page.
My component does have the service 'Autowired' in already. So currently I can just call 'this.data = this.service.getData()' but that call is within my ngOnInit method so it only happens once, and the data field does not get updated when the service's data field gets updated.
You can create a messaging service that publishes data for subscribers or implement this functionality in your original service.
I would suggest having a separate messaging service and have relevant components or services publish/subscribe to it.
messaging.service.ts
import { Injectable } from '#angular/core';
import { Subject } from 'rxjs/Subject';
#Injectable()
export class MessagingService {
private sharedValue = new Subject<string>();
// Observable string streams
sharedValue$ = this.sharedValue.asObservable();
// Service message commands
publishData(data: string) {
this.sharedValue.next(data);
}
You would inject the service like this:
constructor(private messagingService: MessagingService ) {}
Publish to the service:
this.messagingService.publishData(sharedValue);
Subscribe to the service:
this.messagingService.sharedValue$.subscribe(
data => {
this.localSharedValue = data;
});
FROM ANSWER BELOW BY: DeborahK (who's courses on Pluralsight everyone should watch)
Actually, all you need is a getter.
Change your 'data' property to a getter and that will do the trick:
get data(): any {
return this.service.getData();
}
Angular change detection will detect any time that data is changed in
the service which will cause it to re-evaluate its bindings and call
this getter to re-get the data.
No need for a fancy service or Subject. :-)
Actually, all you need is a getter.
Change your 'data' property to a getter and that will do the trick:
get data(): any {
return this.service.getData();
}
Angular change detection will detect any time that data is changed in the service which will cause it to re-evaluate its bindings and call this getter to re-get the data.
No need for a fancy service or Subject. :-)
I'm currently trying to achieve a common task when making API calls from within a Vuex store action object, my action currently looks like this:
/**
* check an account activation token
*
*/
[CHECK_ACTIVATION_TOKEN] ({commit}, payload) {
Api.checkActivationToken(payload.token).then((response) => {
if (response.fails()) {
return commit('NEW_MESSAGE', {message: responses.activation[response.code]})
}
return commit('SET_TOKEN')
})
}
I have several such methods carrying out various actions. What I want to be able to do is present a loader when each API call is made, and hide it again once the response is received. I can achieve this like so:
/**
* check an account activation token
*
*/
[CHECK_ACTIVATION_TOKEN] ({commit}, payload) {
commit('SHOW_LOADER')
Api.checkActivationToken(payload.token).then((response) => {
commit('HIDE_LOADER')
if (response.fails()) {
return commit('NEW_MESSAGE', {message: responses.activation[response.code]})
}
return commit('SET_TOKEN')
})
}
But I would need to repeat these SHOW_LOADER/HIDE_LOADER commits in each API call.
What I would like to do is centralise this functionality somewhere so that whenever API calls are made the showing and hiding of the loader is implicitly bound to the calls and not have to include these additional lines each time.
For clarity; the instantiated API is a client layer that sits on top of Axios so that I can prepare the call before firing it off. I've found I can't directly import the store into the client layer or where the Axios events are fired (so that I could centralise the loader visibility there) because Im instantiating the client layer within the vuex module and therefore creates a circular reference when I tried to do so, meaning the store is returned as undefined.
Is what I am trying to do possible through some hook or event that I have yet to come across?
I actually took a different path with this "issue" after reading this GitHub thread and response from Evan You where he talks about decoupling.
Ultimately I decided that by forcing the API layer to have direct knowledge of the store I am tightly coupling the two things together. Therefore I now handle the SHOW and HIDE feature I was looking for in each of the components where the store commits are made, like so:
/**
* check the validity of the reset token
*
*/
checkToken () {
if (!this.token) {
return this.$store.commit('NEW_MESSAGE', {message: 'No activation token found. Unable to continue'})
}
this.showLoader()
this.$store.dispatch('CHECK_ACTIVATION_TOKEN', {token: this.token}).then(this.hideLoader)
},
Here I have defined methods that shortcut the Vuex commits in a Master vue component that each of my components will extend. I then call showLoader when needed and use the promise to determine when the process is complete and call hideLoader there.
This means I have removed presentation logic from both the store and the API layer and kept them where they, arguably, logically belong.
If anyone has any better thoughts on this I'm all ears.
#wostex - thanks for your response!
so i have a directive in vue.js which is pretty handy for having a single point that handles all request through out the app. you can view it at this gist
https://gist.github.com/jkirkby91-2/261fee5667efcf81648ab2a1a1c33c1b
but every form that uses this to process the request handles the response data completely different.
so is it possible i can pass a call back function to the ajax directive to handle the response data.
so for example i have a form that creates a new posts id like to pass a function that handles that response, i also have a search form that with the response data id like to handle to add markers to my map.
Can you provide an example of how you are using the directive?
I see that you have a parameter called "complete" in your gist. Do you intend to use it like this?
<your-ajax-component v-bind:complete="some_callback_fn()"></your-ajax-component>
This is not the intended use for params. [params] is only for passing data to a child component.
You should use Custom Events to pass data from your child component to parent. The button counter (with two buttons and a main counter) is a great example.
Similarly, you can use $emit() from your ajax component as follows:
// your-ajax-component
export default {
methods: {
doSomething: function (e) {
this.$http.post("/api/some-url", {data}).then(response => {
// your http action is done
// now use $emit to communicate back to parent component
this.$emit("ajax-complete", response) // and you can pass the response data back
}, error => {
// your http action failed
this.$emit("ajax-failed", error) // parent component can handle this error from server
})
}
}
}
Now from the template of your other components / routes, you can insert your-ajax-component and listen to events as follows:
<your-ajax-component v-on:ajax-complete="some_callback" v-on:ajax-failed="error_callback"></your-ajax-component>
Note: directives serve a very different purpose. It is for getting access to DOM element, so that you can do something like focusing the element - put the cursor into a text box.
The documentation for Custom Directives provides examples that are related only to DOM manipulation, and not parent-child communications.
I'm new to meteor.js. Still getting used to it.
I get how templates update reactively according to the cursor updates on the server, like this:
{{#if waitingforsomething.length}} Something Happened! {{/if}}
This is good to display elements on the page, updating lists and content. Now, my question is: what if I want to call some javascript or fire some event when something gets updated reactively? What would be the right way to do it with meteor.js?
Anything inside Tracker.autorun or template instance this.autorun runs with changes in reactive data sources inside these autoruns.
Reactive data sources are ReactiveVar instances, db queries, Session variables, etc.
Template.myTemplate.onCreated(function() {
// Let's define some reactive data source
this.reactive = new ReactiveVar(0);
// And put it inside this.autorun
this.autorun(() => console.log(this.reactive.get()));
});
Template.myTemplate.events({
// Now whenever you click we assign new value
// to our reactive var and this fires
// our console.log
'click'(event, template) {
let inc = template.reactive.get() + 1;
template.reactive.set(inc);
}
});
It is a little bit outdated, but Sacha Greif's Reactivity Basics is a very quick and concise introduction to meteor's reactivity model.
Basically, you have what's called reactive computations, code that observes special data objects (sessions, subscriptions, cursors, etc.) and gets executed whenever any of these reactive sources changes.
This is exposed via the Tracker API
Computation works pretty well for me:
Template.myTemplate.onRendered(function() {
this.computation = Deps.autorun(function () {
if (something) {
$(".reactive").html("Something Happened!");
}
});
});
Template.myTemplate.destroyed = function(){
if (this.computation){
this.computation.stop()
}
};
I Hope this helps.