Using 'this' inside AngularJS services - angularjs

Please check the code below. I've edited a ionic example of popup just to illustrate.
On showAlert1(), I reference 'this' as is and it doesn't work. On showAlert2(), I use a auxiliar variable '_this' that receives 'this' and it does works.
I've seen this kind of thing happening in other occasions, and I believe it's 'Controller as' syntax scope related, but why does this happens?
angular.module('myApp', ['ionic'])
.controller('PopupCtrl',function($scope, $ionicPopup) {
this.testAlert = function() {
alert('Alerting!');
};
this.showAlert1 = function() {
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}, this).then(function() {
this.testAlert();
});
};
this.showAlert2 = function() {
var _this = this;
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}, _this).then(function() {
_this.testAlert();
});
};
});
Here's a Code Pen: http://codepen.io/anon/pen/dPJVNN
Thanks!

"this" in javascript is not the same as "this" in other languages. You can think of it more as the context of a function call.
The default call context on a web application is window. However, when calling a function that is a property of an object, the context becomes the object.
So, in your example:
angular.module('myApp', ['ionic'])
.controller('PopupCtrl',function($scope, $ionicPopup) {
//"this" refers to the controller instance here (controllers are created by angular with the "new" operator)
this.testAlert = function() {
//inside of this function, "this" will still be the controller
alert('Alerting!');
};
//"this" is the controller
this.showAlert1 = function() {
//"this" is still the controller
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}, this).then(function() {
//"this" is no longer the controller. It's probably "window", but it's possible that ionic sets this to some other parameter when it invokes the function.
//since it's not the controller, testAlert() is undefined!
this.testAlert();
});
};
//"this" is the controller
this.showAlert2 = function() {
//"this" is still the controller, and you have assigned _this to also be the controller
var _this = this;
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}, _this).then(function() {
//"_this" is captured by the closure created by the function call, and it is still the controller, so testAlert() is defined.
_this.testAlert();
});
};
});
You'll often see this in code:
var self = this;
With "self" being used in place of this in order to avoid the confusion that you've encountered.
angular.module('myApp', ['ionic'])
.controller('PopupCtrl',function($scope, $ionicPopup) {
var self = this;
self.testAlert = function() {
alert('Alerting!');
};
self.showAlert1 = function() {
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}, self).then(function() {
self.testAlert();
});
};
self.showAlert2 = function() {
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}, self).then(function() {
self.testAlert();
});
};
});

The keyword 'this' refers to the current object it's in. So in your showAlert1() function, the 'this' refers to the $ionicPopup object.
So for example
var parent = {
value: 'parent',
alert: function(){
alert(this.value);
},
child: {
alert: function(){
alert(this.value);
}
}
}
if you do a parent.alert(), it will alert parent. But if you do a parent.child.alert() it will give you undefined because the 'this.value' for child doesnt exist, it doesnt refer to parent.value. So what this means is that this refers to the current object.

"this" in the .then() callback in showAlert1 is referring to the global object. Try this out:
angular.module('mySuperApp', ['ionic'])
.controller('PopupCtrl',function($scope, $ionicPopup) {
this.testAlert = function() {
alert('Alerting!');
};
this.showAlert1 = function() {
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}, this).then(function() {
alert(this === window);
this.testAlert();
});
};
this.showAlert2 = function() {
var _this = this;
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}, _this).then(function() {
_this.testAlert();
});
};
});
"this" is only working for showAlert1 and showAlert2 because you're referring to them as a property of the controller itself right here:
<button class="button button-primary" ng-click="popup.testAlert()">
What is preferable is using $scope:
<html ng-app="mySuperApp">
<head>
<meta charset="utf-8">
<title>
Popups
</title>
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
<link href="//code.ionicframework.com/nightly/css/ionic.css" rel="stylesheet">
<script src="//code.ionicframework.com/nightly/js/ionic.bundle.js"></script>
</head>
<body class="padding" ng-controller="PopupCtrl">
<button class="button button-primary" ng-click="testAlert()">
Test Alert
</button>
<button class="button button-positive" ng-click="showAlert1()">
Alert1
</button>
<button class="button button-positive" ng-click="showAlert2()">
Alert2
</button>
<script id="popup-template.html" type="text/ng-template">
<input ng-model="data.wifi" type="text" placeholder="Password">
</script>
</body>
</html>
then the JS:
angular.module('mySuperApp', ['ionic'])
.controller('PopupCtrl',function($scope, $ionicPopup) {
$scope.testAlert = function() {
alert('Alerting!');
};
$scope.showAlert1 = function() {
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}).then(function() {
$scope.testAlert();
});
};
$scope.showAlert2 = function() {
$ionicPopup.alert({
title: 'Don\'t eat that!',
template: 'It might taste good'
}).then(function() {
$scope.testAlert();
});
};
});

Related

Read Countless Backbone Tutorials But Still Missing Something

UPDATE
I finally learned Backbone back in late 2017. I'd delete this post but StackOverflow says it's not wise to delete answered questions. Please ignore this question.
I've read countless posts here on StackExchange as well as countless tutorials across the Internet but I seem to be just off from understanding basic Backbone use and implementation.
I'm attempting to build a custom Twitter timeline using pre-filtered JSON that is generated from a PHP file on my work's server.
I feel close but I just can't seem to get things to work. At times I'm able to view 20 tweets in my console but am only able to get 1 tweet to render via my template.
Here is my current Backbone setup:
(function($){
if(!this.hasOwnProperty("app")){ this.app = {}; }
app.global = this;
app.api = {};
app.api.Tweet = Backbone.Model.extend({
defaults: {}
});
app.api.Tweets = Backbone.Collection.extend({
model: usarugby.api.Tweet,
url: "https://custom.path.to/api/tweets/index.php",
parse: function(data){
return data;
}
});
app.api.TweetsView = Backbone.View.extend({
el: $('#tweet-wrap'),
initialize: function(){
_.bindAll(this, 'render');
this.collection = new app.api.Tweets();
this.collection.bind('reset', function(tweets) {
tweets.each(function(){
this.render();
});
});
return this;
},
render: function() {
this.collection.fetch({
success: function(tweets){
var template = _.template($('#tweet-cloud').html());
$(tweets).each(function(i){
$(this).html(template({
'pic': tweets.models[i].attributes.user.profile_image_url,
'text': tweets.models[i].attributes.text,
'meta': tweets.models[i].attributes.created_at
}));
});
$(this.el).append(tweets);
}
});
}
});
new app.api.TweetsView();
}(jQuery));
And here is my current HTML and template:
<div id="header-wrap"></div>
<div id="tweet-wrap"></div>
<script type="text/template" id="tweet-cloud">
<div class="tweet">
<div class="tweet-thumb"><img src="<%= pic %>" /></div>
<div class="tweet-text"><%= text %></div>
<div class="tweet-metadata"><%= meta %></div>
</div>
</script>
<script> if(!window.app) window.app = {}; </script>
I also have a CodePen available for testing. Any advice would be greatly appreciated.
Like the comments suggest, additional reading and code rewrite may be needed. The simplest example for a view rendering multiple views is here adrianmejia's backbone tutorial example.
The snippet below includes an additional view and a couple of added functions along with updating the render and initialize functions. Search for 'cfa' to review changes.
(function($){
if(!this.hasOwnProperty("app")){ this.app = {}; }
app.global = this;
app.api = {};
app.api.Tweet = Backbone.Model.extend({
idAttribute: 'id_str'
});
app.api.Tweets = Backbone.Collection.extend({
model: app.api.Tweet,
url: "https://cdn.usarugby.org/api/tweets/index.php",
parse: function(data){
return data;
}
});
app.api.TweetView = Backbone.View.extend({
tagName: 'div',
template: _.template($('#tweet-cloud').html()),
initialize: function(){
},
render: function(){
var j = {};
j.pic = this.model.get('user').profile_image_url;
j.text = this.model.get('text');
j.meta = this.model.get('meta');
this.$el.html(this.template(j));
return this;
},
});
app.api.TweetsView = Backbone.View.extend({
el: $('#tweet-wrap'),
initialize: function(){
this.collection = new app.api.Tweets();
this.collection.on('reset', this.onReset, this);
this.collection.on('add', this.renderATweet, this);
this.collection.fetch();
},
onReset: function(){
this.$el.html('');
this.collection.each(this.renderATweet, this);
},
renderATweet: function (tweet) {
var tweetView = new app.api.TweetView({ model: tweet });
this.$el.append(tweetView.render().el);
},
});
}(jQuery));
$(document).ready(function(){
new app.api.TweetsView();
});
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.3/backbone-min.js"></script>
<script src="https://static.usarugby.org/lib.min.js"></script>
<div id="header-wrap"></div>
<div id="tweet-wrap"></div>
<script type="text/template" id="tweet-cloud">
<div class="tweet">
<div class="tweet-thumb"><img src="<%= pic %>" /></div>
<div class="tweet-text">
<%= text %>
</div>
<div class="tweet-metadata">
<%= meta %>
</div>
</div>
</script>
<div id="footer-wrap"></div>
<script>
if(!window.app) window.app = {};
</script>

Angular $watch function after change variable

I´m trying to build a delayed message box. I observe that apply is called when the app is started, as describe in the API docs. But when the observed value is changed, it isn´t called. The MessageCtrl is inner controller. Why isn´t watch called after changing message var?
angular.module('myApp',[]).controller('MessageCtrl', function($scope) {
$scope.getMessage = function() {
setTimeout(function() {
$scope.$parent.message = {text : ""};
$scope.$apply(function() {
console.log('message:' + $scope.$parent.message.text);
});
}, 2000);
}
$scope.getMessage();
})
.controller('MainCtrl',function($scope){
$scope.message={text:"oi"};
$scope.$watch("message", function(newValue, oldValue){
console.log("watch " + $scope.message.text);
});
});
The inner controller MessageCtrl will get the text message and show it for 2 seconds.
<body ng-app="myApp" ng-controller="MainCtrl">
<div ng-controller="MessageCtrl">
Message:{{message.text}}
</div>
</body>
As far as I can tell your code does work. You are however using $apply incorrectly. $apply lets you inform angular that you are changing state outside of the usual methods and that is should thus re-evaluate bindings etc. So you should be using
$scope.$apply(function() { $scope.$parent.message = {text: 'new message'}; });
angular.module('myApp', []).controller('MessageCtrl', function($scope) {
$scope.getMessage = function() {
setTimeout(function() {
$scope.$apply(function() {
$scope.$parent.message = {
text: "new message"
};
console.log('message:' + $scope.$parent.message.text);
});
}, 2000);
}
$scope.getMessage();
})
.controller('MainCtrl', function($scope) {
$scope.message = {
text: "oi"
};
$scope.$watch("message", function(newValue, oldValue) {
console.log("watch " + $scope.message.text);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainCtrl">
<div ng-controller="MessageCtrl">
Message:{{message.text}}
</div>
</div>
One thing of note, you should really use the angular $timeout service which allows you to set timeouts in your app, but you don't have to handle calling $apply.
angular.module('myApp', []).controller('MessageCtrl', function($scope, $timeout) {
$scope.getMessage = function() {
$timeout(function() {
$scope.$parent.message = {
text: "new message"
};
console.log('message:' + $scope.$parent.message.text);
}, 2000);
}
$scope.getMessage();
})
.controller('MainCtrl', function($scope) {
$scope.message = {
text: "oi"
};
$scope.$watch("message", function(newValue, oldValue) {
console.log("watch " + $scope.message.text);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainCtrl">
<div ng-controller="MessageCtrl">
Message:{{message.text}}
</div>
</div>
You need to use $scope.apply method because setTimeout function is not angular native function :
setTimeout(function() {
$scope.apply({
$scope.$parent.message = {text : ""}; $scope.$apply(function() { console.log('message:' + $scope.$parent.message.text); });}) ;}, 2000);
Alternatively you can also use $timeout inbuilt-service in angular like this :
angular.module('myApp',[]).controller('MessageCtrl', function($scope, , $timeout) { $scope.getMessage = function() { $, $timeout(function() { $scope.$parent.message = {text : ""}; $scope.$apply(function() { console.log('message:' + $scope.$parent.message.text); }); }, 2000); } $scope.getMessage(); })
use $timeout instead $apply that executes $apply implicity
angular.module('myApp',[]).controller('MessageCtrl', function($scope, $timeout) {
$scope.getMessage = function() {
$timeout(function() {
$scope.$parent.message = {text : ""};
$scope.$apply(function() {
console.log('message:' + $scope.$parent.message.text);
});
}, 2000);
}
$scope.getMessage();
})

limit the text length of angular-translate translation

Given the reference example of angular translate:
var app = angular.module('at', ['pascalprecht.translate']);
app.config(function ($translateProvider) {
$translateProvider.translations('en', {
TITLE: 'Hello',
FOO: 'This is a paragraph.',
BUTTON_LANG_EN: 'english',
BUTTON_LANG_DE: 'german'
});
$translateProvider.translations('de', {
TITLE: 'Hallo',
FOO: 'Dies ist ein Paragraph.',
BUTTON_LANG_EN: 'englisch',
BUTTON_LANG_DE: 'deutsch'
});
$translateProvider.preferredLanguage('en');
});
app.controller('Ctrl', function ($scope, $translate) {
$scope.changeLanguage = function (key) {
$translate.use(key);
};
});
I want dynamically add a limitTo option which limits the length of the translation showed.
Lets say i have controller ctrl with variable x
ctrl.x='FOO'
Then i have a html snippet
<span translate="ctrl.x"></span>
I would like somehow to write
<span translate="ctrl.x" limitTo=7>
and then the output would be
This is
How do i accomplish this?

Create a simple Bootstrap Yes/No confirmation or just notification alert in AngularJS

It's so simple in a non-Angular environment. Just html and a two line of js code to show a modal confirmation dialog on the screen.
Now I am developting an AngularJS project in which I am using ui-bootstrap modal confirmation dialogs all over the place and I am sick of creating new controllers even for simple things like "Are you sure to delete this record?" kind of stuff.
How do you handle these simple situations? I am sure some people wrote some directives to simplify the needs.
I am asking you to share your experiences or the projects you know about that subject.
so create a reusable service for that... read here
code here:
angular.module('yourModuleName').service('modalService', ['$modal',
// NB: For Angular-bootstrap 0.14.0 or later, use $uibModal above instead of $modal
function ($modal) {
var modalDefaults = {
backdrop: true,
keyboard: true,
modalFade: true,
templateUrl: '/app/partials/modal.html'
};
var modalOptions = {
closeButtonText: 'Close',
actionButtonText: 'OK',
headerText: 'Proceed?',
bodyText: 'Perform this action?'
};
this.showModal = function (customModalDefaults, customModalOptions) {
if (!customModalDefaults) customModalDefaults = {};
customModalDefaults.backdrop = 'static';
return this.show(customModalDefaults, customModalOptions);
};
this.show = function (customModalDefaults, customModalOptions) {
//Create temp objects to work with since we're in a singleton service
var tempModalDefaults = {};
var tempModalOptions = {};
//Map angular-ui modal custom defaults to modal defaults defined in service
angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
//Map modal.html $scope custom properties to defaults defined in service
angular.extend(tempModalOptions, modalOptions, customModalOptions);
if (!tempModalDefaults.controller) {
tempModalDefaults.controller = function ($scope, $modalInstance) {
$scope.modalOptions = tempModalOptions;
$scope.modalOptions.ok = function (result) {
$modalInstance.close(result);
};
$scope.modalOptions.close = function (result) {
$modalInstance.dismiss('cancel');
};
};
}
return $modal.open(tempModalDefaults).result;
};
}]);
html for display
<div class="modal-header">
<h3>{{modalOptions.headerText}}</h3>
</div>
<div class="modal-body">
<p>{{modalOptions.bodyText}}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn"
data-ng-click="modalOptions.close()">{{modalOptions.closeButtonText}}</button>
<button class="btn btn-primary"
data-ng-click="modalOptions.ok();">{{modalOptions.actionButtonText}}</button>
</div>
once this is done... you just have to inject above service whereever you want to create a dialog box, example below
$scope.deleteCustomer = function () {
var custName = $scope.customer.firstName + ' ' + $scope.customer.lastName;
var modalOptions = {
closeButtonText: 'Cancel',
actionButtonText: 'Delete Customer',
headerText: 'Delete ' + custName + '?',
bodyText: 'Are you sure you want to delete this customer?'
};
modalService.showModal({}, modalOptions)
.then(function (result) {
//your-custom-logic
});
}
You can see my example. whatever i'v done.
<div ng-app="myApp" ng-controller="firstCtrl">
<button ng-click="delete(1);">Delete </button>
</div>
script
var app = angular.module("myApp", []);
app.controller('firstCtrl', ['$scope','$window', function($scope,$window) {
$scope.delete = function(id) {
deleteUser = $window.confirm('Are you sure you want to delete the Ad?');
if(deleteUser){
//Your action will goes here
alert('Yes i want to delete');
}
};
}])
You can use the Angular Confirm library.
When included, it's became available as a directive:
<button type="button" ng-click="delete()" confirm="Are you sure?">Delete</button>
As well as a service:
angular.module('MyApp')
.controller('MyController', function($scope, $confirm) {
$scope.delete = function() {
$confirm({text: 'Are you sure you want to delete?', title: 'Delete it', ok: 'Yes', cancel: 'No'})
.then(function() {
// send delete request...
});
};
});
For anything that has code that is triggered with a ng-click I just add a confirm attribute
eg
<a confirm="Are you sure?" ng-click="..."></a>
and confirm comes from (not mine, found on the web)
app.controller('ConfirmModalController', function($scope, $modalInstance, data) {
$scope.data = angular.copy(data);
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}).value('$confirmModalDefaults', {
template: '<div class="modal-header"><h3 class="modal-title">Confirm</h3></div><div class="modal-body">{{data.text}}</div><div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>',
controller: 'ConfirmModalController'
}).factory('$confirm', function($modal, $confirmModalDefaults) {
return function(data, settings) {
settings = angular.extend($confirmModalDefaults, (settings || {}));
data = data || {};
if ('templateUrl' in settings && 'template' in settings) {
delete settings.template;
}
settings.resolve = { data: function() { return data; } };
return $modal.open(settings).result;
};
})
.directive('confirm', function($confirm) {
return {
priority: 1,
restrict: 'A',
scope: {
confirmIf: "=",
ngClick: '&',
confirm: '#'
},
link: function(scope, element, attrs) {
function reBind(func) {
element.unbind("click").bind("click", function() {
func();
});
}
function bindConfirm() {
$confirm({ text: scope.confirm }).then(scope.ngClick);
}
if ('confirmIf' in attrs) {
scope.$watch('confirmIf', function(newVal) {
if (newVal) {
reBind(bindConfirm);
} else {
reBind(function() {
scope.$apply(scope.ngClick);
});
}
});
} else {
reBind(bindConfirm);
}
}
}
})
My google FOO has failed me and I cannot find the source site for this. I will update if I find it.
You can create a simple factory like this
angular.module('app')
.factory('modalService', [
'$modal', function ($modal) {
var self = this;
var modalInstance = null;
self.open = function (scope, path) {
modalInstance = $modal.open({
templateUrl: path,
scope: scope
});
};
self.close = function () {
modalInstance.dismiss('close');
};
return self;
}
]);
In your controller
angular.module('app').controller('yourController',
['$scope','modalService',function($scope,modalService){
$scope.openModal=function(){
modalService.open($scope,'modal template path goes here');
};
$scope.closeModal=function(){
modalService.close();
//do something on modal close
};
}]);
I have passed $scope in service function so that you can access closeModal function and in case you want to access some data from your controller .
In your html
<button ng-click="openModal()">Open Modal</button>

Mithril with angularjs

I am a newbie to Mithril JS framework and trying to integrate Mitril view with angularJS. Has anyone tried this before?
I want to check how can we bind the angular controller methods to click events of elements created in Mitril.
I got this working by having this code
var e = document.getElementById('elementId');
var scope = angular.element(e).scope();
m("a[href='javascript:;']", {
onclick : scope.someMethod
}, "Test");
But I am not sure if this is right way to do this.
I'd say that is not idiomatic angular code.
A more idiomatic way might be to use a directive on the Angular side, and pass in an event dispatcher controller to the view on the mithril side:
//mithril code
var testWidget = function(ctrl) {
return m("a[href='javascript:;']", {onclick: ctrl.onclick}, "Test")
}
//angular code
angular.module("foo").directive("testWidget", function() {
return {
restrict: "E",
link: function($scope, element, attrs) {
var template = testWidget({
onclick: function() {
$scope.$apply(function() {
$scope.$eval(attrs.onclick)
})
}
})
m.render(element, template)
}
}
})
angular.module("foo").controller("MyCtrl", function() {
this.doStuff = function() {
console.log("called doStuff")
}
})
<div ng-controller="MyCtrl as c">
<test-widget onclick="c.doStuff()"></test-widget>
</div>
// Code goes here
(function() {
'use strict';
angular
.module('app', [])
.directive('testMithrilScope', testMithrilScope)
.controller('MyCtrl', MyCtrl);
var testMithrilWidgetScope = function(ctrl) {
return m("a[href='javascript:;']", {onclick: ctrl.directiveclick}, ctrl.text)
}
var htmllinks = [
{text: "Link 1 "},
{text: "Link 2 "},
{text: "Link 3 "},
{text: "Link 4 "},
{text: "Link 5 "},
{text: "Link 6 "}
];
function testMithrilScope() {
return {
restrict: "E",
scope : {
htmlclick: '&'
},
link: function($scope, element, attrs) {
function makeList1() {
return m('ul', htmllinks.map(function(a, index){
return m('li', testMithrilWidgetScope({
directiveclick : function() {
var data = {
arg1: a.text
}
$scope.htmlclick(data);
},
text : a.text
})
);
}));
}
var template1 = makeList1();
m.render(element[0], template1)
}
}
}
function MyCtrl() {
this.doStuff = function(text) {
console.log("You clicked: " + text)
}
}
})();
<!DOCTYPE html>
<html>
<head>
<script data-require="angularjs#1.5.8" data-semver="1.5.8" src="https://opensource.keycdn.com/angularjs/1.5.8/angular.min.js"></script>
<script data-require="mithril#0.2.4" data-semver="0.2.4" src="https://cdn.jsdelivr.net/mithril/0.2.4/mithril.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app">
<div ng-controller="MyCtrl as ctrl">
<test-mithril-scope htmlclick="ctrl.doStuff(arg1)"></test-mithril-scope>
</div>
</body>
</html>

Resources