Angularjs caches ajax data into a service - ajax

I have such a simple scenario,
App starts from Main View (/main), then click top right button to Sub View (/sub).
During app launching app.run(), user's profile will be loaded into a service userService, once if user went to Sub View, this profile will be read from that service userService then display, here is the code,
app.run(function($http, $rootScope, userService){
$http.get('/profile').then(function(result){
userService.setProfile(result.data.profile);
});
});
app.service('userService', function(){
var user = {}
this.setProfile(profile){
user.profile = profile;
};
this.getProfile(){
return user.profile;
}
});
In Sub View, getProfile() was invoked to display the info.
It works if user start from Main View -> button -> Sub View, however, if user manually refreshed Sub View or just start from Sub View, getProfile() will get nothing to display,I know that's because before the promise of getting profile returned, Sub View had been proceed.
I don't like to read profile from Sub View directly and dynamically because I have other pages need profile info as well, so is there any workaround or better design? thanks.

Instead of using app.run you should probably utilize your route provider for this. Whether you use ngRoute or ui-router they both have resolve functionality. Instead of getting your profile in app.run you should probably move that to userService as well.
app.service('userService', function(){
var self = this;
self.user = {};
self.getProfile = function() {
return self.user.profile;
};
self.init = function() {
return $http.get('/profile').then(function(result){
self.user.profile = result.data.profile;
});
};
});
Now that your service is more factory like, you can utilize the initialization of it in the route provider. I use ui-router but this can easily be applied to ngRoute as well.
I start by creating an abstract state that handles the resolve which I can than 'import' in whichever other states I need.
.state('init', {
abstract: true,
resolve: ['userService', function(userService) {
return userService.init();
}]
});
Now I just use it in other states and I can assure that the userService is initialized.
.state('subView', {
parent: 'init',
url: '/subView'
//other state stuff like template, controller, etc
});

The way I've worked around that is add the relevant data to $window.sessionStorage, roughly like this (you'll need to make $window available):
app.service('userService', function(){
var user = {}
if ($window.sessionStorage.profile)
this.user.profile = JSON.parse($window.sessionStorage.profile)
this.setProfile(profile){
user.profile = profile;
this.$window.sessionStorage.profile = JSON.stringify(profile)
};
this.getProfile(){
return user.profile;
}
});

Related

Behavior Subject RXJS

I have this link that changes the final of the url with a time stamp:
getAvatar(channelId): BehaviorSubject<string> {
return new BehaviorSubject(`${link}?${new Date().getTime()}`);
}
And in the Avatar Component, that is a child of at least 10 other components i call this subscribe:
getAvatar() {
this.userService.getAvatar(this.channelId)
.subscribe(res => {
this.avatar = res;
this.cdr.detectChanges();
this.cdr.markForCheck();
});
}
OBS: Im using the OnPush changeDetection strategy
And in another component i have a function that changes this profile picture inside the link:
this.userService.changeProfilePicture(picture, this.myChannelId)
.subscribe(
() => {
this.loading.hide();
this.userService.getAvatar(this.id);
this.screenService.showToastMessage('Foto de perfil alterada.', true);
}
As you can see, im recalling the getAvatar() function that returns a BehaviorSubject to generate another link and the AvatarComponent doenst detect the change of the behaviorSubject, what im doing wrong ?
And theres another way to recall the getAvatar() function of all the AvatarComponent instances to reload each avatar instance ?
OBS2: I tried to use of rxjs operator, creating a new Observable(), tried the Subject class, all of those seems to not get detected by the subscribe inside the AvatarComponent
OBS3: I tried to get the AvatarComponent with #ViewChild() and call this this.avatarCmp.getAvatar(); to reload the avatar, but reloads just one instance of the Avatar Component
your service needs to be more like this:
private avatarSource = new BehaviorSubject(`${link}?${new Date().getTime()}`);
avatar$ = this.avatarSource.asObservable();
setAvatar(channelId): BehaviorSubject<string> {
this.avatarSource.next(`${link}?${new Date().getTime()}`);
}
then you need to subscribe to avatar$ and update with setAvatar

How can i override placeOrder() action in Magento 2

I'm newbie in Magento. My shop should work with a web service. I have to check availability of products from web service before magento creates a new order. And after creating order successful i have to send the orderId back to web service. All this actions should be execute when a customer confirm a button "place order".
In a picture you see an "Place Order". I not sure how Magento does create a new order. I assume that an action placeOrder() will be call. My aim is to put a method checkAvailability() before this action and and method sendOrderId() after this action. checkAvailability() and SendOrderId() are the methods from webservice.
Has somebody an idea, how and where can i do that?
Sorry about bad english. Thank you
If you need to overwrite a function instead a class method (I used to overwrite Magento_Checkout/js/action/place-order).
requirejs-config.js
var config = {
config: {
mixins: {
'Magento_Checkout/js/action/place-order': {
'My_Module/js/action/place-order': true
}
}
}
};
place-order.js
define(['mage/utils/wrapper'], function (wrapper) {
'use strict';
return function (placeOrderAction) {
return wrapper.wrap(placeOrderAction, function (originalAction, paymentData, redirectOnSuccess) {
// my own code here
return originalAction(paymentData, redirectOnSuccess);
});
};
});
For your requirement, you need to used this event.
Used this event observer to check checkAvailability()
checkout_onepage_controller_success_action
Used this event observer to used SendOrderId()
sales_order_place_after
I had a similar case. I needed to override placeOrder action that was announced in third part module (Amasty_Checkout).
So, my solution was to create mixin in my theme.
1) Announce the mixin in theme with myTheme/Amasty_Checkout/requirejs-config.js:
var config = {
config: {
mixins: {
'Amasty_Checkout/js/view/onepage': {
'Amasty_Checkout/js/view/onepage-extend': true
}
}
}
};
2) Add mixin myTheme/Amasty_Checkout/web/js/view/onepage-extend.js with code:
define(
[
'jquery',
'uiComponent',
'ko',
'uiRegistry',
'Magento_Checkout/js/model/quote',
'Amasty_Checkout/js/action/set-shipping-information',
'Amasty_Checkout/js/model/agreement-validator',
'Amasty_Checkout/js/model/agreement-validator-old',
'Magento_Checkout/js/model/payment/additional-validators',
'Amasty_Checkout/js/model/amalert',
'mage/translate'
],
function (
$,
Component,
ko,
registry,
quote,
setShippingInformationAction,
checkoutValidator,
checkoutValidatorOld,
additionalValidators,
alert,
$t
) {
'use strict';
var mixin = {
placeOrder: function () {
// Here you put your extended code
}
};
return function (target) { // target == Result that Magento_Ui/.../default returns.
return target.extend(mixin); // new result that all other modules receive
};
});
Note that in my case I copied all content in define[...] section from original module script ('Amasty_Checkout/js/view/onepage') that I needed to override.
Here is the resource that helped me with my solution https://github.com/magento/magento2/issues/1864#issuecomment-141112927
I hope this will help someone save time.

Event each time component becomes visible

Is there a way in Angular2 to have an event fired when my component becomes visible?
It is placed in a tabcontrol and I want to be notified when the user switches. I'd like my component to fire an event.
What I finally did (which is not very beautiful but works while I don't have a better way to do it...) is to use the ngAfterContentChecked() callback and handle the change myself.
#ViewChild('map') m;
private isVisible: boolean = false;
ngAfterContentChecked(): void
{
if (this.isVisible == false && this.m.nativeElement.offsetParent != null)
{
console.log('isVisible switched from false to true');
this.isVisible = true;
this.Refresh();
}
else if (this.isVisible == true && this.m.nativeElement.offsetParent == null)
{
console.log('isVisible switched from true to false');
this.isVisible = false;
}
}
There is no such event, but if you're using a tab control, the proper way to do this would be to create a tab change #Output for your tab control if it's custom, otherwise, most tab controls (like ng-bootstrap) have some tab change event as well.
If your component has to be aware of this, you can use this tab change event to detect which tab is visible, and if you know which tab is visible, you also know if your component is visible or not. So you can do something like this:
onTabChange(event) {
this.currentTab = /** Get current tab */;
}
And then you can send it to your component itself if you have an input:
#Input() activated: boolean = false;
And then you can apply it with:
<my-component [activated]="currentTab == 'tabWithComponent'"></my-component>
Now you can listen to OnChanges to see if the model value activated changed to true.
You can also refactor this to use a service with an Observable like this:
#Injectable()
export class TabService {
observable: Observable<any>;
observer;
constructor() {
this.observable = Observable.create(function(observer) {
this.observer = observer;
});
}
}
When a component wishes to listen to these changes, it can subscribe to tabService.observable. When your tab changes, you can push new items to it with tabService.observer.next().
You can use the ngAfterViewInit() callback
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html
Update
The new Intersection Observer API can be used for that
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
See also https://stackoverflow.com/a/44670818/217408
For those watching at home, you can now use ngAfterContentInit() for this, at least on Ionic anyway.
https://angular.io/guide/lifecycle-hooks
Best way to work around this limitation of Angular is to use a shared service that provides a Subject your component can subscribe to. That way new values could be pushed onto the Observable and the components which subscribe get the newest data and can act accordingly.
Fyi: The difference between a normal Observable and a Subject is that a Subject is multicast whereas an Observable could only be subscribed to by one Subscriber.
As a small example I show you a possible implementation of a shared-service and following the subscription inside the component that needs this new data.
Shared-service:
// ...
private actualNumberSubject = new Subject<number>()
public actualNumber$ = this.actualNumberSubject.asObservable()
/**
* #info CONSTRUCTOR
*/
constructor() {}
/**
* #info Set actual number
*/
setActualNumber(number: number) {
this.actualNumberSubject.next(internalNumber)
}
// ...
Push new value onto the subject from anywhere where shared.service is imported:
// ...
this.sharedService.setActualNumber(1)
Subscribe to sharedService.actualNumber$ in component to process/display that new data:
// ...
this.sharedService.actualNumber$.subscribe(number => {
console.log(number)
// e.g. load data freshly, etc.
})
// ...
I have the same purpose and cannot get a satisfy approach to it. The first answer will call so many times.
There is a compromised way I used, of course, not elegant either.
In parent component, I set a method:
parentClick() {
setTimeout(() => {
// TO-DO
This.commonService.childMethod();
}, time);
}
Maybe the method not accurate in time, but in some way, you reach the destiny.

Viewcomponent alternative for ajax refresh

I have a viewcomponent that contains some reusable business logic that embed in various pages. This has been working fine. However, I now have a requirement to refresh the viewcomponent using ajax.
Is there any way to accomplish this? From what I have read, it is not possible, although that info was a bit outdated.
If it is not possible, what is the best alternative?
On beta7 it is now possible to return a ViewComponent directly from a controller. Check the MVC/Razor section of the announcement
The new ViewComponentResult in MVC makes it easy to return the result
of a ViewComponent from an action. This allows you to easily expose
the logic of a ViewComponent as a standalone endpoint.
So you could have a simple view component like this:
[ViewComponent(Name = "MyViewComponent")]
public class MyViewComponent : ViewComponent
{
public IViewComponentResult Invoke()
{
var time = DateTime.Now.ToString("h:mm:ss");
return Content($"The current time is {time}");
}
}
Create a method in a controller like:
public IActionResult MyViewComponent()
{
return ViewComponent("MyViewComponent");
}
And do a better job than my quick and dirty ajax refresh:
var container = $("#myComponentContainer");
var refreshComponent = function () {
$.get("/Home/MyViewComponent", function (data) { container.html(data); });
};
$(function () { window.setInterval(refreshComponent, 1000); });
Of course, prior to beta7 you could create a view as the workaround suggested by #eedam or use the approach described in these answers

Using a ui-router resolve to wait for a google map to load

I'm trying to load a google map before loading the dashboard state. Seems like the perfect use for a ui-router resolve.
Unfortunately, the uiGmapIsReady promise is never resolving.
Any help would be greatly appreciated.
state('app.dashboard', {
url: '/dashboard',
templateUrl: 'partials/dashboard.html',
controller: 'dashCtrl',
data: {
authorizedRoles: [USER_ROLES.all]
},
resolve: {
getGoogleMap: function(MapService){
return MapService.getMap();
}
}
})
.factory('MapService', function(uiGmapIsReady, $q) {
var obj = {};
//returns a promise that is resolved only after the google map object created by angular-google-maps is loaded
obj.getMap = function(){
var deferred = $q.defer();
uiGmapIsReady.promise()
.then(function(map){
deferred.resolve(map[0].map);
});
return deferred.promise;
};
return obj;
The controller that was waiting on the resolve was also the controller that establishes the scope variables needed by angular-google-maps to draw the map. That caused this: resolve cause the controller to not get called --> map never gets drawn because it is waiting on the controller to set some scope objects--> resolve that is waiting for map to be drawn never triggers --> controller never gets called

Resources