I've ended up with a lot of logic in my controller which I realise is not good. Therefore I would like to move this to a service.
At the moment the controller accepts a url which will either be from YouTube or Vimeo. It detects whether the string "youtube" or "vimeo" is present in the url and then does what it needs to do accordingly. Here's part of the "logic" that currently resides in the controller:
if url.indexOf("youtube") > -1 {
variable_1 = "Something";
variable_2 = "Something";
//do some more stuff
}
else {
variable_1 = "Something";
variable_2 = "Something";
//do some more stuff
}
$scope.task.items.push("I need to add things to this array too");
A Service is the way to go but my first question is a service or a factory?
This is what I'm working on but I'm not sure how I would pass the variables that exist in the controller (variable_1 and variable_2) back to the controller when the service has completed.
myApp.service('urlService', function() {
this.detectProvider = function() {
if url.indexOf("youtube") > -1 {
}
else {
}
//how can I push things to the $scope array here?
};
});
In your service
myApp.service('urlService', function() {
this.detectProvider = function(url) {
arrayOfMyVars = new Array();
if url.indexOf("youtube") > -1 {
arrayOfMyVars.push("Something");
arrayOfMyVars.push("SomethingElse");
}
else {
arrayOfMyVars.push("Something");
arrayOfMyVars.push("SomethingElse");
}
//how can I push things to the $scope array here?
return arrayOfMyVars;
};
});
in your controller
var res = urlService.detectProvider(url);
variable_1=res[0];
variable_2=res[1];
$scope.task.items.push('the thing you need to push'); // maybe res[2] and in your service you had another arrayOfMyVars.push('the thing you need to push')...
Don't forget to import your service into your controller ;)
A Service is the way to go but my first question is a service or a
factory?
Simply speaking it does not matter. From personal point of view just use what suits you best. A service will create a new instance of the function, a factory will simply execute the function and do not create a new instance of it.
About your second question: Simply return variable_1 and variable_2 in your services method and assign them to your $scope.
myApp.service('urlService', function() {
this.detectProvider = function(url) {
if url.indexOf("youtube") > -1 {
...
return [variable_1, variable_2];
}
else {
...
return [variable_1, variable_2];
}
};
});
Service or Factory both are singleton instances. At the end u will get object only.
In service you will create using functtion constructor
In Factory we can construct it using object literrals
Related
This is a design pattern related question. I am not looking for an answer as to how to achieve the following, but rather the most widely accepted and way to achieve polymorphism in a service.
Suppose I had a service called getData. It needs to get some data, whether it be from a database, text file, or something hardcoded, and output it depending on what the settings are on the $scope in the controller. In this example below, suppose getData depends on the dataSource.
angular.module('testApp').controller('testController'), [$scope, myAwesomeService, function ($scope, myAwesomeService){
$scope.dataSource = 'database'; //defines the source of the data
$scope.getData = function() {
//use myAwesomeService, get the data and output
if($scope.dataSource ==='database') {
return //do it the first way
}
else if($scope.dataSource ==='text') {
return //do it the second way
}
else if($scope.dataSource ==='csvfile') {
return //do it the third way
}
else if($scope.dataSource ==='form') {
return //do it the fourth way
}
}
}]);
Questions:
How would you achieve this generally in Javascript? I am not sure about the best practices around achieving polymorphism in Javascript. I am used to using interfaces and dealing with the situation above by using dependency injection and passing in objects that adhere to the same interface, and call a common method, from the controller. Usually some other "class" would take care of selecting which object to instantiate and pass in, and therefore make the controller agnostic to concrete details as to "how it is done".
How would one go about doing this in AngularJS?
How would the pattern typically look? Can you give a "textbook" Angular way of achieving polymorphism?
I wanted to comment, but I realized it might be too long, so I'm going to post an answer.
If we are talking about ES5, polymorphism & inheritance can be achieved through prototyping.
For example:
function Auto(name,year){
this.year=year;
this.name=name;
}
Auto.prototype.showYear = function(){
console.log(this.year);
}
function Car(name,year, model){
Auto.call(this,name,year);
this.model=model;
}
Car.prototype = Object.create(Auto.prototype);
//usage
var car = new Car('BMW',2015,'320d');
car.showYear(); // will output 2015
In ES6 this can be done using class functions. You can read more about this, HERE ( it's gonna be very nice :D )
Below you'll find some code that might answer your question. Hope this is what you're looking for:
function BaseService(){
this.dataSource='database';
}
BaseService.prototype.getData = function(){
console.log('base: get data');
}
function TextService(){
this.dataSource='text';
}
TextService.prototype = new BaseService();
TextService.prototype.getData = function(){
console.log('child text: get data');
}
function CsvService(){
this.dataSource='csv';
}
CsvService.prototype = new BaseService();
CsvService.prototype.getData = function(){
console.log('child csv: get data');
}
function FormService(){
this.dataSource='form';
}
FormService.prototype = new BaseService();
FormService.prototype.getData = function(){
console.log('child form: get data');
}
angular.module('myApp').factory('awesomeService', function(){
var service={};
service.getData = function(dataSource){
var sourceService;
if(dataSource='database'){
sourceService= new BaseService();
}
if(dataSource=='text'){
sourceService=new TextService();
}
if(dataSource=='csv'){
sourceService = new CsvService();
}
if(dataSource=='form'){
sourceService= new FormService();
}
return sourceService.getData(); // i'm going to assume getData returns a promise
}
return service;
});
angular.module('myApp').controller('myController', function($scope,awesomeService){
var myDataSource='database';
$scope.getData = function(){
awesomeService.getData(myDataSource).then(function(data){
$scope.result=data;
});
}
});
I am working with AngularJS and have created a Module, which has a Factory and a Filter. The Factory gets a local json file translations) and the filter provides a function that returns a translated version of the text. So the code looks like the following;
angular
.module('i18n', [])
.factory('translationDataFact', ['$http', function($http){
var t = {};
var user = {};
t.defaultLanguage = 'en-GB';
t.languageFile = null;
t.init = function(){
t.setLanguage();
if(!t.languageFile){
$http
.get('translations/' + t.defaultLanguage + '.json')
.success(function(data){
t.languageFile = data.strings;
})
.error(function(error){
console.log(error);
});
}
}
t.setLanguage = function(){
/* change default language to User language here */
if(user.id){
t.defaultLanguage = user.language;
}
return t.defaultLanguage;
}
t.init();
return t.languageFile;
}])
.filter('t', ['translationDataFact', function (translationDataFact) {
var translate = function (stringIdenitfier) {
var translation = translationDataFact.languageFile[stringIdenitfier];
if(translation){
return translation;
}
return "translate me!!";
};
return translate(stringIdenitfier);
}]);
Then I wish to use the filter to translate variables and names like this
{{"string" | t }}
The problem I am having is that I have no idea how to make sure
The return of the Factory is set before the Filter runs this.
Also I am confused by how I prevent the whole application rendering until this filter is ready?
Any help would be amazing as I am lost :(
Is there a reason why you don't use an existing angularjs translation library like angular-translate?
In factory method u need to return Service itself, not result of operation. (I am not sure what exactly u want from this serivce)
when you return t.language it is always null and it will remain null in your filter... because your http call is asynchronious.
I would make this like:
app.module('translationDataFact', ['$resource', function($resource) {
var t = {};
t.init = function() {
t.result = $resource('...');
}
t.init()
return t;
}]);
In controller you have:
$scope.language = translationDataFact.result;
You make filter with parameter, inside filter you can check whether language is undefined or not.
So later you write:
{{ "string" | t:language}
And after language 'arrives' you see translation.
To answer your concerns:
Your factory should return something that can be asked for a specific translation. If the translations are not ready just return something basic like an empty string or null. e.g.
return translations.t(languageFile, translationKey);
Where t() would be a function that inspects the internal data structure of translations and can return either the result of the translation or the value mentioned earlier if the translations haven't been loaded yet.
You can do something like ng-show="translations.isLoaded()" on your top level element, but you'd need to set up a reference to the translations service on the $scope of your highest level controller. You may want to do this on the $rootScope so your translation service is always available in controllers as well.
In one of my factories I need to set a variable when data is fetched (through $http) so I can access it in my controller (the intention is to display a spinner image until the data is loaded).
.factory('LoadData', function LoadData($http, $q){
return {
getMyData: function(){
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
// this should be reference to the other method (getLoadStatus)
// where I want to change its value to "true"
// this doesn't work - "this" or "self" don't work either because we're in another function
LoadData.getLoadStatus.status = true;
}
);
},
// by calling the below method from my controller,
// I want to mark the completion of data fetching
getLoadStatus = function(){
var status = null;
return status;
}
}
}
I hope you got the idea - how could this be accomplished? Also, I'm open to any suggestions which are aimed towards a better approach (I want to pick up best practice whenever possible).
Status is essentially a private variable; use it as:
.factory('LoadData', function LoadData($http, $q){
var status = null; // ESSENTIALLY PRIVATE TO THE SERVICE
return {
getMyData: function(){
return $http(...).then(function(response){
...
status = true;
});
},
getLoadStatus = function(){
return status;
}
};
})
There are several ways.
Here's one which I prefer to use:
.factory('LoadData', function LoadData($http, $q){
var status = false;
var service = {
getMyData: getMyData,
status: status
};
return service;
function getMyData() {
return $http(
// implementation of the call
).then(
function(response){
var myData = response.data;
status = true;
}
);
}
}
This provides good encapsulation of your methods and gives you a clean interface to export. No need for the getter method if you don't want it.
Inspiration via John Papa's Angular style guide (found here).
You could simply store variable flag in closure:
.factory('LoadData', function LoadData($http, $q) {
var status = false;
return {
getMyData: function() {
status = false;
return $http(/* implementation of the call */).then(function(response) {
status = true;
return response.data;
});
},
getLoadStatus: function() {
return status;
}
}
});
Also if getMyData loads fresh data every time, it's important to reset status to false before each request.
I actually decide to use a promise in my controller after calling this service and when data is returned, I am simply making the status true. It seems that is best practice to have a promise returned when calling a service so I should be good.
I'm having some basic problems with angular at the moment. I just wrote a service that reads the temperature of an external device in an interval of five seconds. The service saves the new temperature into a variable and exposes it via a return statement. This looks kind of this (simplified code):
angular.service("tempService", ["$interval", function ($interval) {
//revealing module pattern
var m_temp = 0,
requestTemp = function() {//some logic here},
onResponseTemp = function (temp) {
m_temp = temp;
},
//some other private functions and vars ...
foo = bar;
//request new temperture every 5s, calls onResponseTemp after new data got received
$interval(requestTemp, 5000);
return {
getTemp = function(){return m_temp;}
}
}]);
I use a controller to fetch the data from the service like this:
angular.controller("tempCtrl", ["$scope", "tempService", function ($scope, tempService) {
$scope.temp = tempService.getTemp();
}]);
In my view I access it like this:
<div ng-controller="tempCtrl">
<p>{{temp}}</p>
</div>
But I only get 0 and the value never changes. I have tried to implement a custom Pub/Sub pattern so that on a new temperature my service fires an event that my controller is waiting for to update the temperature on the scope. This approach works just fine but I'm not sure if this is the way to go as angular brings data-binding and I thought something this easy had to work by itself ;)
Help is really appreciated.
Please see here http://jsbin.com/wesucefofuyo/1/edit
var app = angular.module('app',[]);
app.service("tempService", ["$interval", function ($interval) {
//revealing module pattern
var m_temp = {
temp:0,
time:null
};
var requestTemp = function() {
m_temp.temp++;
m_temp.time = new Date();
};
var startTemp = function() {
$interval(requestTemp, 3000);
};
return {
startTemp :startTemp,
m_temp:m_temp
};
}]);
app.controller('fCtrl', function($scope,tempService){
$scope.temp = tempService;
$scope.temp.startTemp();
});
You are returning a primitive from your service, if you want to update an primative you need to reftech it. You should return an object, as on object is passed by reference, you get the actual values in your controller.
do this in your service:
return m_temp;
And this in your controller:
$scope.temp = tempService;
and your view will update as soon as the service gets updated.
Does this help you?
i think you should use $interval in controller ot in service
$interval(tempService.getTemp(), 5000);
This is the function that I am working with to call my factory
var myService = function($http) {
return {
bf: null,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
And I am creating this object using
TicTacTorrent.service('AService', ['$http', myService]);
However, when calling AService.initialize() it creates the promise object like it should, but it doesn't update the BF object. I'm confused as to how to update the bf object to be the new value. How would I reference myService.bf since this.bf would create a local instance for .success function?
Try this:
var myService = function($http) {
this.bf = null;
return {
bf: this.bf,
initialize: function() {
this.promise = $http.get(this.server + "/requestkey").success(function(data) {
myService.bf = new Blowfish(data.key);
});
}
}
Where do you want to initialize?
Have you seen the $provider example code?
Search for "provider(name, provider)" and check if it suits your need.
Otherwise I'm unsure what the code you'vew written will run like.
I usually write factories like this:
angular.module('app').factory('myService', ['$http', function($http) {
var publicObj = {};
publicObj.bf = ""; // Just to make sure its initialized correctly.
publicObj.initialize = function() {snip/snap... myService.bf = new Blowfish(data.key);};
return publicObj;
}]);
The difference might be that you previous code returned an inline anonymous object which might have a hard time referring to itself. But by that logic it should work by just making myService return a predeclared var and returning that.