Detect a proxy change vuejs3 - vuejs3

I don't know how to detect a proxy object change without display it.
I Use async API call to set a proxy object (from a parent component).
In my child component, I use it like this
computed: {
user() { return dataProxy.getCurrentUser() // return a proxy object }
}
updated: {
console.debug(this.user)
if(this.user.id !== undefined) {
loadUserDetails(this.user)
}
}
When the component is mounted, updated method is called and the console display an empty proxy object (ok)
But when the proxy object is updated from api response, the updated method is never called.
Now, If I add {{user}} on my template, the updated method is called after the API response with the right content.
how to detect a proxy object change without display it ?

You should use a watch function:
watch: {
user(newUser) {
console.log('user changed:', newUser);
}
}

Related

Can Meteor preserve object references between server and client?

Is it possible to preserve pointers (object references) when you send data from the server to the client with Meteor? My initial tests say No, but perhaps there is another way to do this.
Here are server-side and client-side scripts:
Server.js
Meteor.methods({
test: function test() {
var object = {
key: ['value']
}
var output = {
object: object
, reference: object.key
}
console.log(output.reference[0])
output.object.key[0] = "changed"
console.log(output.reference[0])
return output
}
})
Server output:
// value
// changed
Client.js
Meteor.call("test", testCallback)
function testCallback(error, data) {
if (!error) {
console.log(data.reference)
data.object.key[0]= "edited"
console.log(data.reference)
}
}
Client output in the console:
// changed
// changed
No, this is not possible, although subscriptions do something similar. If your data is not in a collection, you can still publish it (see this.added and friends in the Meteor docs), and the data will show up in a collection in the client.

Subscribe collection based on url parameters in meteor

I am trying to achieve a dynamic subscription of collection data based on the parameter in the url.
On Client side, I have the following subscription code in main.js file.
cSubscribe = Meteor.subscribe('cPublish', Session.get('param1'));
On Server, I have the following publish code in main.js file.
Test = new Meteor.Collection('test');
Meteor.publish('cPublish', function(param1) {
return Test.findOne({_id: param1});
});
In the router, I am setting the url parameter value in the Session, Session.set('param1', value); and when I try cSubscribe.ready(), it is returning false. Until the subscription is ready I am showing a loading template.
The route snippet,
'/test-url/:value': function(value) {
Session.set('param1', value);
if (cSubscribe.ready()) {
//some code
} else {
return 'loading';
}
}
What is wrong with the process ? Is there any better way of achieving dynamic subscription ?
First, I hope, you use iron-router (-:
In router, for example, in onBeforeAction() you have to set Session.set('param1', value);
Then on client try to use smth like this:
Meteor.startup(function() {
Deps.autorun(function() {
var param = Session.get("param1");
if(param) {
cSubscribe = Meteor.subscribe('cPublish', param);
});
});
In other words, you have to resubscribe after url changed.

Capture all $http premises with Angular

I would like to refresh the state of the user that is used in the navbar. but now I have to call .all(function() { refresh() }); on all $http.post premises.
Could I capture all of them with a configuration for instance?
Yes, you can intercept $http using request/response interceptors.
But a better design might be to set a $watch in the controller for the navigation bar on a property the POST calls change (possibly on $rootScope, though it is not recommended).
This is what I ended up doing:
app.config(function($httpProvider, $provide) {
// This sucks but Angular is so compliated that we can't get the rootScope
// in a simple way inside a factory for a configuration
function getRootScope() {
return angular.element('body').scope();
}
// Call refresh() after each HTTP destructive action
$provide.factory('refreshAfterRequests', function($q) {
return {
'response': function(response) {
// Every non-GET request can potentialy change the state of the user
if (response.config.method !== 'GET') {
getRootScope().refresh();
}
return response;
}
};
});
// The refresh is an interceptor on all HTTP requests made with Angular
$httpProvider.interceptors.push('refreshAfterRequests');
});
If someone have something better like how to get the scope from the inside the interceptor, I will gladly change this answer.

AngularJS - refresh view after http request, $rootScope.apply returns $digest already in progress

I am simply trying to load data when my app starts. However, the view loads faster than the http request(of course). I want to refresh my view once my data has been properly loaded because that data defines my view.
I've tried $rootScope.apply from inside the factory where I do my http request, and I also tried directly doing the http request in my controller again with $scope.apply, and neither one worked as they both gave me "$digest already in progress"
Any idea how can I set up my code to make my views refresh on data load? I will be having several different http requests and I would like to know how to set them up properly! I would really appreciate any input!
Here is some of the code I am working with.
app.factory('HttpRequestFactory', function($http, $q) {
var HttpRequestFactory = {
async: function(url, params) {
var deferred = $q.defer();
$http({
url: url,
method: post,
params: params
})
.success(function(data, status, headers, config) {
deferred.resolve(data);
})
.error(function(data, status, headers, config) {
deferred.reject("An error occurred");
});
return deferred.promise;
}
};
return HttpRequestFactory;
});
Factory
function initializeAll(){
HttpRequestFactory.async('../api', {action: 'getall'}).then(function(data) {
//$rootScope.$apply(function () {
allData = data;
//});
angular.forEach(allData, function(value, index){
console.log('Voala!');
});
});
}
Controller calling the factory's function initializeAll()
app.controller("MainController", ["$scope", "$rootScope","MyFactory",
function($scope, $rootScope, MyFactory){
MyFactory.initializeAll();
}
]);
Oh my !
You got the f** matter with AngularJS !
In fact you have to do a "safeApply" like that for example :
$rootScope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
In AngularJS you can only have one $apply or $digest loop at the same time.
For details on these loops look at the docs :
http://docs.angularjs.org/guide/concepts
It will explain what is the $apply loop and you'll understand a lot of things about the two-way-data-binding in AngularJS
Hope it helps.
Don't use $apply: use $watch.
Calling $apply is (almost) always the wrong thing to do. The only time you should ever be calling it is if you've triggered a change outside of an 'angular' method; here, since the trigger occurs in an angular $http request, you can't call $apply because it's already being done at that moment by the $http block. Instead, what you want to do is $watch.
Official Doc for $scope.$watch() here
This will let you watch an object and update whenever it changes. I assume that your view is based on allData and you want it to update immediately; if you're using an ng method, then the watch is automatically setup for you and no more work should be needed. If you're using allData yourself inside a controller, you can write the watch in that controller like this:
$scope.$watch(function thingYouWantToWatch(){
return <accessor call to allData here>;
},
function whatToDoOnChange(newValue, oldValue){
$scope.myCoolThing = newValue; //this is the newValue of allData, or whatever you're watching.
}
);

I need two instances of AngularJS $http service or what?

I want add a response interceptor to my $http service for error handling purposes. The interceptor logic include send errors messages to server using $http in case necessary, BUT I don't want send errors messages to the server about errors messages, I mean, I want disable my interceptor while sending error message to the server.
My idea was create a service named 'remote_log' and put inside it all the code needed to send error to server. That service of course will use the $http service and have it in its dependency list.
Then add as dependency of the interceptor to the 'remote_log' service, and use the 'remote_log' inside the interceptor when need send errors to the server. The problems is that:
Interceptors must be defined using the $httpProvider when the $http service still is not instantiated/accessible, so, inside the interceptor code can't be a dependency to that the $http service because a "Circular dependency" error happen.
I think my only option is create a separate instance of the $http service inside my 'remote_log', an instance that don't uses the $httpProvider configuration I set while creating the interceptor. My question is: How can I do that? Any other ideas?
1. Circular dependency problem.
So, why does the error appear? Here is a quick overview of the process:
$http service is requested.
$httpProvider is asked to construct it.
During construction you register interceptor, that requests $http service not existing yet.
You get "Circular dependency" error.
First solution.
Create your dependency using angular.injector(). Notice, that you will create another $http service, independent from your app.
$httpProvider.interceptors.push(function($q) {
$injector = angular.injector();
return {
response: function(response) {
$injector.invoke(function($http) {
// This is the exterior $http service!
// This interceptor will not affect it.
});
}
};
});
Second solution (better).
Inject $injector in your interceptor and use it to retrieve dependencies after $http initialization, right at the time you need them. These dependencies are registered services of your app and will not be created anew!
$httpProvider.interceptors.push(function($q, $injector) {
return {
response: function(response) {
$injector.invoke(function($http, someService) {
// $http is already constructed at the time and you may
// use it, just as any other service registered in your
// app module and modules on which app depends on.
});
}
};
});
2. Interception prevention problem.
If you use the second solution, there are actually two problems:
If you utilize $http service inside your
interceptor, you may end up with infinite interceptions: you send
request, interceptor catches it, sends another, catches another,
send again, and so on.
Sometimes you want just prevent request from being intercepted.
The 'config' parameter of $http service is just an object. You may create a convention, providing custom parameters and recognizing them in your interceptors.
For example, let's add "nointercept" property to config and try duplicate every user request. This is a silly application, but useful example to understand the behavior:
$httpProvider.interceptors.push(function($q, $injector) {
return {
response: function(response) {
if (response.config.nointercept) {
return $q.when(response); // let it pass
} else {
var defer = $q.defer();
$injector.invoke(function($http) {
// This modification prevents interception:
response.config.nointercept = true;
// Reuse modified config and send the same request again:
$http(response.config)
.then(function(resp) { defer.resolve(resp); },
function(resp) { defer.reject(resp); });
});
return defer.promise;
}
}
};
});
Having the testing for property in interceptor, you may prevent the interception in controllers and services:
app.controller('myController', function($http) {
// The second parameter is actually 'config', see API docs.
// This query will not be duplicated by the interceptor.
$http.get('/foo/bar', {nointercept: true})
.success(function(data) {
// ...
});
});
I used what is described in the answer but I used the syntax with a factory because with the anonymous function it didn't work, I don't really know why:
(function(angular){
angular.module('app', [])
.config([
'$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('Interceptor');
}
])
.factory('Interceptor', [
'$injector',
InterceptorFactory
]);
function InterceptorFactory($injector){
return {
request: function(config) {
var ServiceWithHttp = $injector.get('ServiceWithHttp');
// Use ServiceWithHttp
return config;
}
};
}
}(window.angular));

Resources