How do I pass data through an ES6 "class" function in angular - javascript

I'm trying to create a controller in angular as an ES6 class. This works as expected ( able to print the string returned through the message function). See below.
<div ng-controller="ProfilingCtrl as profileData">
{{profileData.message()}}
</div>
But what I want to do now i to be able to use a function class in ng-repeat, like below.
<div ng-controller="ProfilingCtrl as profileData">
<div ng-reapeat="x in profileData.getData()">
{{x[0]}}
</div>
</div>
getData() is a function returning an array of strings. Is this even possible? If not, how would one go about doing such an operation?

Big chance your issue is coming from ng-reapeat -> typo should be ng-repeat. Still helping you with your code:
It would be useful if you shared your controller code. But I'll make something up.
First, unless you are trying to display the first character of each string in your data array, remove the [0], as x represents the element resulting from iteration.
<div ng-controller="ProfilingCtrl as profileData">
<div ng-repeat="x in profileData.getData()">
{{x}}
</div>
</div>
Further recommendation: bind your data to a variable, instead of a function. ES6 style (though I would still stick with functions for controllers)
class MyController {
constructor(myDataService) {
this.myDataService = myDataService;
this.myData = []; // This is where the data will be
}
handleError(error) {
// do something error
}
getData() {
this.myDataService.getData() // this is some method that returns a promise
.then(response => this.myData = response) // response from http in a promise
.catch(error => this.handleError(error));
}
$onInit() { // angular will take care of starting this off
this.getData();
}
}
Note the $onInit is called by Angular when the component is ready. Read about lifecycle hooks: https://toddmotto.com/angular-1-5-lifecycle-hooks
The lifecycle hook is outside of scope of your question, but still nice to get into.
Note that the comment made earlier about "bind it to $scope" is not recommended. ControllerAs as you are doing is better, could later be put elsewhere though.

Related

How does the following variable allocation work?

This is probably a simple question, but can someone pls clarify the following javascript code?
// 'response' is the JSON data gotten from the backend:
....then(response => {
this.myVar = response.data;
});
// 'myVar' is initialised here:
data: function() {
return {
myVar: null
};
}
I can see how we declare a property 'data' that points to a function that returns an initialised variable 'myVar'.
I don't quite get how 'myVar' is set to the response value. I assume it is a JS technique? Can someone please explain this or provide me a link to such?
* EDIT *
From the responses, it seems I didn't supply enough info (sorry about that - I was assuming it be a simple JS issue). I extracted this code from a tutorial, and it works. The code excerpt is from a .vue file. So I supply the whole file here. The question is still the same.
<template>
<div>
<button class="my-button" v-text="myVar.name" :type="type">My Button</button>
</div>
</template>
<script>
export default {
mounted() {
// The "api/vue" route calls a controller that supplies a JSON object with a single name=>value pair
axios.post("api/vue", {}).then(response => {
// ?? How exactly does myVar get allocated the JSON data?
this.myVar = response.data;
});
},
// initiate the 'myVar' variable
data: function() {
return {
// ?? this object is delared here and somehow accessible to the whole file?
myVar: null
};
},
};
</script>
this whole code makes a Vue Component. In a Vue Component, initial data should be a function, which will be invoked when component created. the object that returned by data() is the initial data for the component, and it's observable(observable means, if you change it, something other will be changed also). the techniche of javascript used here is Object.defineProperty : https://v2.vuejs.org/v2/guide/reactivity.html
base on your code, it says you have a initial data named 'myVar', if you change the value of 'myVar', the dom which bound with 'myVar' will change automatically. in your code, it's the text of the button.
mounted
this is a life-cycle hook, it will be invoked after the component mounted into the dom.
you called an AJAX at here, then you used arrow function to deal with the result of AJAX. arrow function means it's this will not change (here , this equals the Vue Component).
we already have a initial data 'myVar' on this component, now we change it:
this.myVar = response.data;
because it's observable/reactive, the text of the button in your template will be changed.
by the way, your initial myVar is null, so your initial text myVar.name will cause an error
// 'response' is the JSON data gotten from the backend:
....then(response => {
this.myVar = response.data;
});
In this first method, you are fetching data from a server, and assigning it this.myVar (not myVar). so it's local to the component, however...
In the code below, whenever you call data(), you are returning an object with a property myVar encapsulated in it. it is not the same as this.myVar.
// 'myVar' is initialised here:
data: function() {
return {
myVar: null
};
}
I'm not sure what you're trying to achieve here, but these variables are unrelated. And maybe the naming needs some work too, so it's easier for us to see what the purpose of this code is. Either way, if this.myVar is a variable owned by your component, the object returned from the data() method will not have any effect on it, since it's structure is different. the myVar in the Object you are returning in data is a new variable, local to that object.

then() returning a null value when it should be pending

Ember 2.17
I am calling an helper from my template :
{{#each invoice.invoiceLines as |line| }}
{{pricings/full-pricing line.pricing}}
{{/each}}
invoice,invoiceLine, as well as pricing are ember models.
Here is how invoice is created in model () :
model(params) {
let invoice= this.store.findRecord('invoice',params.invoice_id)
return Ember.RSVP.hash({
invoice: invoice,
allShares: invoice.then((i)=>{return i.allShares()}),
detailShares: invoice.then((i)=>{return i.detailShares()})
});
}
The goal of the helper is to take pricing, extract numbers (everything is in the model, no more relations) and return a string formatting the initial price and the subscription price.
The helper is as following :
import { helper } from '#ember/component/helper';
export function pricingsFullPricing([pricing]) {
return pricing.then(
p=>{
debugger
},p=>{
}
)
}
export default helper(pricingsFullPricing);
When I run the page, debugger is called twice (the template loop run once).
First time p is null, the second time it is a pricing.
Isn't then supposed to prevent that? Why does it behave like that?
Your route is wrong, routes are promise aware (that's what hash is for), it should be:
model(params) {
return Ember.RSVP.hash({
invoice: this.store.findRecord('invoice',params.invoice_id)
//allShares: invoice.then((i)=>{return i.allShares()}),
//detailShares: invoice.then((i)=>{return i.detailShares()})
});
}
Then your handlebars is just:
{{#each model.invoice.invoiceLines as |line| }}
{{line}}
{{/each}}
You also shouldn't call methods like you are on a model. It's not really clear what allShares(), etc does but these should (probably) be computed in the controller. Something along the lines of:
import { computed } from '#ember/object';
export default Controller.extend({
allShares:computed('model.invoice', function(){
return this.get('model.invoice').allShares();
});
});
Though this doesn't seem ideal. Like I said, it's hard to be explicit as it's not clear what your trying to do here. It'd probably make more sense if your extracted these methods into a service.
You then don't need the helper at all. This appears to be just trying to work around promises.
It makes life a lot easier if you try and load all server side data in the route before load.
First rule of helpers
Each time the input to a helper changes, the compute function will be called again.
Second, there's nothing about helpers that will make this block subsequent calls because you are returning a promise.
export function pricingsFullPricing([pricing]) {
return pricing.then(
p=>{
debugger
},p=>{
}
)
}
You've created a simple helper here that will use the promise itself as the value. Look at ember-promise-helpers/await to see how a class based helper is used to manually set the value that's displayed in the template.
Now, if you're wondering why the recomputation is happening, I'm going to have to speculate based off the knowledge I have of Ember data just from being part of the Ember community (I've never actually used Ember Data). You know line.pricing is a promise? I can then assume your using some sort of relationship, which will most likely have to be loaded via an ajax call (hence the promise). But these relationships in Ember data, iirc, use this PromiseProxyMixin that allow them to behave simultaneously like a promise or like an object (depending on whether the data is in the store already or not). This is what allows you to reference the promise in your template without then
See this article for a better understanding of what I mean

Is there any difference (performance) if I run ng-repeat over object or function that returns object

I'm binding an array in my service to the local scope in controller (pass by reference):
myApp.controller('TheController', function($scope, theService){
$scope.theArray = theService.array;
})
in Service:
//...
theService.array = [];
$http.get('/array').then(function(data){
theService.array = angular.extends(theService.array, data);// here I'm updating theService.array with new values;
})
Problem with that binding is that I can't break the reference to the initial array. so I have to make some extra function in order to detect what the diference between new and old array.
A work around would be to initialize the array every time and return a function in my service:
$http.get('/array').then(function(data){
theService.array = data;
})
theService.getArray = function(){
return theService.array;
};
in controller it would be:
myApp.controller('TheController', function($scope, theService){
$scope.theArray = theService.getArray;
})
in my html (and here is the question), I will have:
<div ng-repeat="data in theArray()">....</div>
instead of <div ng-repeat="data in theArray ">....</div>
Is there any problem concerning performance in ng-repeat?
As far as I'm concerned, in AngularJS' digest cycles all functions in the template is going to be called again (like what is happening when you put a function inside ng-if) and they may cause another digest cycles to be runned.
See this and this for further description, though they are not really talking about this matter particularly.
So I would rather run ng-repeat over the object and update the values occasionally when they really should be updated.

AngularJS OneTime Binding with ng-repeat doesn't let me refresh scope

I have a list that I am using ng-repeat and one-time binding because there is no need for me to update once I have the data.
I was hoping that I could use one-time binding and force a digest, but I get the common error that a $digest is already being processed or whatever it is.
My code looks like the following
$scope.$on(IonicEvents.beforeEnter, function() {
Locations.getAllLocations()
.then(function(allLocations) {
$scope.model.locations = allLocations;
//I have tried $scope.$digest()..
//also $scope.$apply()...
}, function(error) {
$log.error('Error retrieving locations');
})
});
My ng-repeat in my template looks like the following:
<ion-item class="item" ng-repeat="location in ::model.locations">
<div class="row">
<div class="col">
<p>{{::location.name}}</p>
</div>
</div>
</ion-item>
Any ideas, or am I completely off-track?
I think First you may check if(!$scope.$$phase) then apply your proccess
$scope.$on(IonicEvents.beforeEnter, function() {
if(!$scope.$$phase) {
Locations.getAllLocations()
.then(function(allLocations) {
$scope.model.locations = allLocations;
//I have tried $scope.$digest()..
//also $scope.$apply()...
}, function(error) {
$log.error('Error retrieving locations');
})
}
});
Yes I think you are off-track.
My idea for your particular situation would be using a clone object (with no reference to the main object) once they are retrieved from Locations.getAllLocations() using
$scope.clonedLocationObj = angular.copy($scope.model.locations);
and render this object to your view so that it won't effect your main object.
As far as one way binding is concerned, Angular doesn't let you change the one time binded model again because when we declare a value such as {{ ::foo }} inside the DOM, once this foo value becomes defined, Angular will render it, unbind it from the watchers and thus reduce the volume of bindings inside the $digest loop. Simple!
angular one time binding syntax article
Hope it helps.

Set radio button state based on a value received from REST in AngularJS

What I would like to achieve is set a radio button's state according to the data retrieved from the server in AngularJS.
What makes this more special is that I need to show a div based on a condition (the car has 1 and only 1 assigned to it).
If the car is so-called isSingleUser, the div contains the radio buttons needs to be visible, otherwise we need to hide it.
Finally, after a lot of struggling, I even have the solution to achieve this, but I ran into some interesting obstacles what I want to share with others for reference and I still have some questions regarding the alternative "solutions".
The simplest (and working) solution
We have 3 cars, the Cars service will GET the particular car according to the chosen link.
Only car1 and car2 are isSingleUser so the div should only visible for those cars.
You can see that the radio button's state changes according to the
car.isMonitoringAutoEnablementSet JSON property.
This was my first attempt, but I couldn't see the code working at that time, I used the
value property of the input, wrongly, because the ng-value is better.
More specifically, the value property on input cannot handle boolean values as booleans (and not stringified)
therefore angular couldn't map the boolean value false to "false" and the same goes with the true value.
If I used ng-value, everything is fine.
Note that the plateNumber for each car is visible on the page.
Let's see what I tried afterwards:
Magic with promise
My assumption was that the data for car is not downloaded yet and the template rendered
before the controller received the data and store it to $scope.car
So I added this code for the controller:
$scope.car = Cars.get({carId: $routeParams.carId})
.$promise.then(function(car) {
$scope.automonitoring_state = car.isMonitoringAutoEnablementSet;
$scope.isSingleUser = car.isSingleUser;
});
and modified the view according to the new variables:
<h1 class="title">{{car.plateNumber}}</h1>
<div class='ng-hide' ng-show='isSingleUser'>
<p class='paragraph'>automatic
<form name="myForm" class="toggle">
<input type="radio" name="state" ng-model="automonitoring_state" ng-value="true">on
<input type="radio" name="state" ng-model="automonitoring_state" ng-value="false">off
<button class="button" disabled>save</button>
</form>
</p>
</div>
Note that the plateNumbers are disappeared from the page, clearly showing that some problem
occured while fetching the data for the car variable. Only the new variables had a
value in the scope (automonitoring_state and isSingleUser).
Somehow even if I put a simple console.log() inside the then function on the promise, the
car variable will not hold the data for the car, strange...
This solution also seems like a workaround, because I need to define new variables in the
scope, but if I save the car's state through the service, I need to sync back my new
variables to the $scope.car so this one is definitely a no-go for me.
Resolve with the help of a controller scope function
Then I googled and googled, found some advices on Stackoverflow, and so on.
Hey, there is resolve which is good for passing some variables into controllers before the view is rendered, that's exactly what I want.
Naively, I tried to invoke a controller function from the routeProvider like this :
when('/driver/cars/:carId', {
templateUrl: 'partials/driver_car.html',
controller: 'DriverCarController',
resolve: 'DriverCarController'.resolveCar
})
and of course added the function in question to the controller:
$scope.resolveCar = {
car: ['$routeParams', 'Cars',
function($routeParams, Cars) {
return Cars.get({
carId: $routeParams.carId
}).then(function(car) {
console.log('resolve');
return car;
}, function() {
return [];
});
}
]
};
The result is: nothing, no console.log at all, proving that the function was not invoked.
Another working solution with passing the variable from resolve
This trial is a slightly modified version of the solution above.
The Cars service is only used from the $routeProvider, if the promise returns the value, it is saved to the car variable.
.when('/driver/cars/:carId', {
templateUrl: 'partials/driver_car.html',
controller: 'DriverCarController',
resolve: {
car : function (Cars,$route) {
return Cars.get({carId: $route.current.params.carId})
.$promise.then(function (response) {
return response;
});
}
}
})
The only thing to change in the controller is to add 'car' to the injection list and save
the parameter to the $scope.
controllers.controller('DriverCarController', ['$scope', '$routeParams','car', 'SecurityEvents',
function($scope, $routeParams, car, SecurityEvents) {
$scope.car = car;
console.log(car);
}
]);
Please see my public plunkers, you can try out each solution I described above: plunkers
Any help is appreciated about judging between the most viable approach, providing some information about the intended use cases of each alternative.
Thanks!
Number 2 is the most similar to what I have used.
To fix the bug, just change the code in the controller like this:
Cars.get({
carId: $routeParams.carId
}).$promise.then(function(car) {
$scope.automonitoring_state = car.isMonitoringAutoEnablementSet;
$scope.isSingleUser = car.isSingleUser;
$scope.car = car;
});
The biggest problem that I see with calling the service from the resolve is that it tightly couples the controller with the app router, which means your controller is less re-useable.

Categories

Resources