RxJs and Angular1 component - how to avoid $scope? - angularjs

Pseudo code for angular 1.5 component with RxJs:
component('demo', {
template: `<div>
<div ng-if="verificationFailed">Sorry, failed to verify</div>
<button ng-if="continueEnabled">Continue</button>
<button ng-click="verify()">Verify</button>
</div>`,
controllerAs: 'ctrl',
bindings: {
someOptions: '='
},
controller: ($scope, someService) => {
var ctrl = this;
ctrl.continueEnabled = false;
ctrl.verificationFailed = false;
ctrl.verify = function() {
Rx
.Observable
.interval(10 * 1000)
.timeout(2 * 60 * 1000)
.flatMapLatest(_ => { someService.verify(ctrl.someOptions.id)})
.retry(1)
.filter((result) => { result.completed })
.take(1)
.subscribe(_ => {
$scope.$evalAsync(_ => {
ctrl.continueEnabled = true
});
}, _ => {
$scope.$evalAsync(() => {
ctrl.verificationFailed = true;
});
});
};
}
});
Any way to avoid using $scope with $evalAsync to trigger digest? Without it the view is simply not updating.
Why? Because there is no $scope on angular2 and i want to make migration as easy as it is possible

You can use angular1-async-filter. Take a look at this good article:
http://cvuorinen.net/2016/05/using-rxjs-observables-with-angularjs-1/
Here is an example:
(function(angular) {
var myComponent = (function () {
function myComponent() {
this.template = "<div><br/> Time: {{ctrl.time | async:this}}</div>";
this.controllerAs = 'ctrl';
this.controller = "myController";
}
return myComponent;
}());
var myController = (function() {
function myController() {
this.time = Rx.Observable.interval(1000).take(50);
}
return myController;
}());
angular.module('myApp', ['asyncFilter']);
angular.module('myApp').component('myComponent', new myComponent());
angular.module('myApp').controller('myController', myController);
})(window.angular);
See it working on Plunker:
https://plnkr.co/edit/80S3AG?p=preview

Related

$compile is not compiling angular template in jasmine after upgrading the angular version from 1.6 to 1.8 - unit testing

We have a code like this which was working well before. But started to fail post version upgrade from angular 1.6 to 1.8.
Looks like it needs some changes in source code but unable to identify the spot. This is happening due to the version upgrade.
Please help me to fix this unit tests. All our tests are failing.
(function (angular, moment, TimeShift) {
'use strict';
describe('references', function() {
var NORMAL_MAIL = {
'projectId': 1234
};
var BIM_UI_INTEGRATION = {
bimModuleKey: "BIM"
};
function setCurrentTime(zoneOffset, dateUTC) {
TimeShift.setTimezoneOffset(zoneOffset);
TimeShift.setTime(new Date(dateUTC).getTime());
}
beforeEach(module('directiveModule'));
beforeEach(function() {
window.Date = TimeShift.Date;
test.mocks.registeri18nFilter();
});
afterEach(function () {
window.Date = TimeShift.OriginalDate;
});
beforeEach(inject(function($rootScope, $compile) {
this.$rootScope = $rootScope;
this.$compile = $compile;
}));
beforeEach(function() {
setCurrentTime(-660, 0);
var element = angular.element('<references></references>');
this.element = this.$compile(element)(this.$rootScope.$new());
});
describe('no model references', function() {
it('should not display anything', function() {
this.$rootScope.mail = NORMAL_MAIL;
this.$rootScope.bimUiIntegration = BIM_UI_INTEGRATION;
this.$rootScope.isPreviewingFromEditMail = false;
this.$rootScope.$digest();
expect(this.element.find('header').length).toBe(0);
});
});
});
})(window.angular, window.moment, window.TimeShift);
And our source code is here.
(function(angular) {
angular
.module('directiveModule')
.component('references', {
templateUrl: 'mail/view/directives/mailModelsReferences/references.tpl.html',
controller: referencesController,
controllerAs: 'vm',
bindings: {
mail: '<',
bimUiIntegration: '<',
isPreviewingFromEditMail: '='
}
});
ReferencesController.$inject = ['$filter'];
function ReferencesController($filter) {
const vm = this;
let i18n = $filter('i18n');
vm.hasModelReferences = hasModelReferences;
vm.toDateString = toDateString;
vm.getBimModuleName = getBimModuleName;
if (hasModelReferences()) {
vm.model = vm.mail.models.model;
vm.viewpoint = vm.mail.models.viewpoint;
}
function hasModelReferences() {
let models = vm.mail ? vm.mail.models : vm.mail;
return models && (models.model || models.viewpoint);
}
function toDateString(dateField) {
if (dateField && dateField.value) {
let fieldValue = dateField.value;
let momentVal;
if (angular.isArray(fieldValue)) {
momentVal = window.moment(`${fieldValue[0]}-${fieldValue[1]}-${fieldValue[2]}`, 'YYYY-M-D');
} else {
momentVal = window.moment(fieldValue);
}
if (momentVal && momentVal.isValid()) {
return momentVal.format('LL');
}
}
return "";
}
function getBimModuleName() {
return i18n(vm.bimUiIntegration.bimModuleKey);
}
}
})(window.angular);

Angular JS watch factory variable

I have a factory variable which is shared by two directives. One directives changes it and I need it to update the value in other directive as well.
This is my factory code
commonApp.factory('setSmsMessage', [function () {
var factoryObj = {
data: {
isSms: false
},
setSms: function () {
factoryObj.data.isSms = true;
},
reset: function () {
factoryObj.data.isSms = false;
}
};
return factoryObj;
}]);
This is my one directive
commonApp.directive('osSendMessage', ['setSmsMessage',
function (setSmsMessage) {
return {
restrict: 'A',
scope: {
sent: '='
},
link: function (scope, element, attrs) {
},
controller: function ($scope) {
$scope.setSmsMessage = setSmsMessage.data;
$scope.$watch('setSmsMessage.isSms', function (newValue, oldValue, scope) {
console.log(setSmsMessage.data.isSms);
}, true);
},
templateUrl: "/Static/js/AngularApps/MessageCenter/sendmessage/Templates/sendmessages.html"
};
}]);
This is my other directive which sets the variable
commonApp.directive('osMessageCenterMenu', ['setSmsMessage', function (setSmsMessage) {
return {
restrict: 'A',
scope: {
messages: "="
},
controller: function ($scope) {
},
link: function (scope) {
// opening sms
$("#toggleSendSmsMessage").click(function () {
scope.openSendSms();
});
scope.openSendSms = function () {
console.log("from menu: " + setSmsMessage.data.isSms);
setSmsMessage.setSms();
console.log("from menu: " + setSmsMessage.data.isSms);
$("#SendMessageForm").toggle();
}
// end here
,
templateUrl: "/Static/js/AngularApps/MessageCenter/Templates/messageCenterMenu.html"
};
}]);
But the value is not updated in the other controller which is listening to it
A better way would be to store the isSms property in a global object like so:
commonApp.factory('setSmsMessage', [function ()
{
var factoryObj = {
data: {
isSms: false
},
setSms: function ()
{
factoryObj.data.isSms = true;
},
reset: function ()
{
factoryObj.data.isSms = false;
}
};
return factoryObj;
}]);
Now in your controller link the $scope to the factories global object which holds the isSms property.
// Link setSmsMessage to the factories data object
$scope.setSmsMessage = setSmsMessage.data;
// Now call the setSms() function. The $scope.setSmsMessage will be updated after the call.
setSmsMessage.setSms();
// You can now watch for changes in the $scope like so
$scope.$watch('setSmsMessage.isSms', function (newValue, oldValue, scope) {
console.log(setSmsMessage.data.isSms);
}, true);
Working demo
(function() {
var commonApp = angular.module("app", []);
commonApp.factory('setSmsMessage', [function ()
{
var factoryObj = {
data: {
isSms: false
},
setSms: function ()
{
factoryObj.data.isSms = true;
},
reset: function ()
{
factoryObj.data.isSms = false;
}
};
return factoryObj;
}]);
commonApp.controller("smsController", function($scope, setSmsMessage) {
// Link setSmsMessage to the factories data object
$scope.setSmsMessage = setSmsMessage.data;
// Now call the setSms() function. The $scope.setSmsMessage will be updated after the call.
setSmsMessage.setSms();
$scope.reset = function()
{
setSmsMessage.reset();
}
$scope.$watch('setSmsMessage.isSms', function (newValue, oldValue, scope) {
console.log(setSmsMessage.data.isSms);
}, true);
});
commonApp.directive('osMessageCenterMenu', ['setSmsMessage', function (setSmsMessage)
{
return {
restrict: 'A',
scope: {
messages: "="
},
controller: function ($scope)
{
},
link: function (scope)
{
console.log(scope);
// opening sms
$("#toggleSendSmsMessage").click(function ()
{
scope.openSendSms();
});
scope.openSendSms = function ()
{
console.log("from menu: " + setSmsMessage.data.isSms);
setSmsMessage.setSms();
console.log("from menu: " + setSmsMessage.data.isSms);
$("#SendMessageForm").toggle();
}
}
}
}]);
})()
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="smsController">
<div os-message-center-menu>
<button id="toggleSendSmsMessage">reset message</button>
</div>
</div>
</div>
you can also work with $rootScope in your factory and emit an event with custom data and then subscribe to the event in all your controllers
Also, try naming your factory based on functionality, for obvious
reasons.
Following is what am trying to say :-
(function() {
angular
.module("myApp", [])
.factory('smsMessage', ['$rootScope', function($rootScope) {
var factoryObj = {
data: {
isSms: false
},
setSms: function() {
factoryObj.data.isSms = true;
$rootScope.$emit('SET', {isSms : true});
},
reSet: function() {
factoryObj.data.isSms = false;
$rootScope.$emit('RE-SET', {isSms : false});
}
};
return factoryObj;
}])
.controller("controllerOne", function($scope, $rootScope, smsMessage) {
$scope.setSmsMessage = smsMessage.data;
$scope.set = function(){
smsMessage.setSms();
}
$scope.reset = function(){
smsMessage.reSet();
}
$rootScope.$on('SET', function(event, data) {
alert('SET in controllerOne');
$scope.setSmsMessage = data.isSms;
});
$rootScope.$on('RE-SET', function(event, data) {
alert('RE-SET in controllerOne');
$scope.setSmsMessage = data.isSms;
});
})
.controller("controllerTwo", function($scope, $rootScope, smsMessage) {
$scope.setSmsMessage = smsMessage.data;
$scope.set = function(){
smsMessage.setSms();
}
$scope.reset = function(){
smsMessage.reSet();
}
$rootScope.$on('SET', function(event, data) {
alert('SET in controllerTwo');
$scope.setSmsMessage = data.isSms;
});
$rootScope.$on('RE-SET', function(event, data) {
alert('RE-SET in controllerTwo');
$scope.setSmsMessage = data.isSms;
});
});
})()
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="controllerOne">
<label>Controller One</label>
<button ng-click="set()">SET</button>
<button ng-click="reset()">RE-SET</button>
</div>
<div ng-controller="controllerTwo">
<label>Controller Two</label>
<button ng-click="set()">SET</button>
<button ng-click="reset()">RE-SET</button>
</div>
</div>

AngularJS: after-select-item not triggering

I am using angularjs version 1.6.4 with angular-multiple-select module for multi selecting. Every thing is working fine. I am able to select from suggestions but whenever i do selection "after-select-item" directive is not triggering. According to angular-multiple-select module documentation
afterSelectItem : Listen for event before adding an item
<div class="form-group float-label-control">
<label>Skills</label>
<multiple-autocomplete ng-model="model.user.skills"
object-property="name"
after-select-item="model.afterSelectItem"
suggestions-arr="model.skills">
</multiple-autocomplete>
</div>
My controller few code lines:
(function () {
"use strict";
var module = angular.module(__appName);
function fetchSkills($http) {
return $http.get(__apiRoot + "/skills")
.then(function (response) {
return response.data;
})
}
function controller($http) {
var model = this;
model.$onInit = function () {
fetchSkills($http).then(function (skills) {
model.skills = skills;
});
};
model.afterSelectItem = function (item) {
console.log("after select item");
console.log(item);
}
}
module.component("userEdit", {
templateUrl: "components/user-edit/user-edit.template.html",
bindings: {
userId: "<",
onUserSaved: "&"
},
controllerAs: "model",
controller: ["$http", controller]
});
}());

Calling parent directive method from child directive through attrs

This is the scenario :
<export-team>
<ul>
<li>
<button buy-ticket="{{data}}" buy-callback="onBuyTicket()">buy</button>
</li>
<li>
<button buy-ticket="{{data}}" buy-callback="onBuyTicket()">buy</button>
</li>
</ul>
</export-team>
The buyTicket directive
(function() {
'use strict';
angular
.module('myApp')
.directive('buyTicket', buyTicket);
/** #ngInject */
function buyTicket($parse, ngDialog, authService, APPCONFIG, $rootScope, shareToken, contestsFactory, shareCurrentTicket, shareIdSession, shareSessionAAMS, $location) {
var vm = this;
var directive = {
restrict: 'A',
link : function(scope, element, attributes) {
var buyCompatible = attributes['buyCompatible'];
function addZero(i) {
if (i < 10) {
i = "0" + i;
}
return i;
}
var buyTicket = function(contest) {
var d = new Date();
var y = d.getFullYear();
var m = addZero(d.getMonth()+1);
var day = addZero(d.getDate());
var h = addZero(d.getHours());
var min = addZero(d.getMinutes());
var s = addZero(d.getSeconds());
var date = ''+y+m+day+h+min+s+'';
var transactionId = $rootScope.TRANSACTIONID;
var currentTOKEN = shareToken.get();
var data = {
idSessione:currentTOKEN, // ->TOKEN
userAgent:navigator.userAgent,
sessioneAAMS:contest.aams_session_id,
gameId:APPCONFIG.GAME_ID,
transactionId:transactionId,
dateTime:date,
buyIn:contest.buy_in
}
var dialogLoading = ngDialog.open({
closeByDocument : false,
closeByEscape : false,
showClose : false,
id : 'ft-modal-loading',
controller: ['$scope', function($scope){
$scope.bodyUrl = 'app/components/modals/body/loading.html';
$scope.title = 'Acquisto Ticket';
$scope.error = 'Il sistema sta procedendo all\'acquisto del ticket';
}]
});
contestsFactory.buyTicket(data).success(function(response){
dialogLoading.close();
if (response.esito == "0") {
if (!buyCompatible) {
shareCurrentTicket.set(response.ticketSogei);
shareSessionAAMS.set(contest.aams_session_id);
shareIdSession.set(contest.id_session);
$location.path('my-contests/'+contest.id_contest+'/'+contest.contest_status);
}
} else {
var message = response.descrizione;
var ids = ngDialog.getOpenDialogs();
var dialogError = ngDialog.open({
id : "ft-modal-error-2",
controller: ['$scope', function($scope){
$scope.bodyUrl = 'app/components/modals/body/error.html';
$scope.title = 'Errore';
$scope.error = message;
}]
});
}
})
.error(function(){
var dialogErrorNotEndled = ngDialog.close('ft-modal-loading');
ngDialog.open({
id : 'ft-modal-error',
controller: ['$scope', function($scope){
$scope.bodyUrl = 'app/components/modals/body/error.html';
$scope.title = 'Errore';
$scope.error = 'Il servizio non รจ attualmente disponibile';
}]
});
})
}
var openConfirmBuyTicket = function(contest) {
contest = JSON.parse(contest);
if (ngDialog.isOpen('ft-modal-contest-detail')) {
ngDialog.close('ft-modal-contest-detail');
};
if (!authService.isLogged()) {
ngDialog.open({
controller: ['$scope', function($scope){
$scope.bodyUrl = 'app/components/modals/body/not_logged.html';
$scope.title = 'Spiacenti';
$scope.error = 'Devi essere loggato per poter partecipare ad un contest';
}]
});
} else {
ngDialog.openConfirm({
controller: ['$scope', function($scope){
$scope.title = 'CONFERMA';
$scope.bodyUrl = 'app/components/modals/body/confirm_buy.html';
$scope.contest_name = contest.name_contest;
$scope.buy_in = contest.buy_in;
$scope.currency = APPCONFIG.CURRENCY_SYMBOL;
}],
}).then(function (confirm) {
buyTicket(contest);
}, function(reject) {
});
}
}
element.on('click', function(e){
var contest = attributes['buyTicket'];
openConfirmBuyTicket(contest);
})
}
};
return directive;
}
})();
The export directive
(function() {
'use strict';
angular
.module('myApp')
.directive('exportTeam', exportTeam);
/** #ngInject */
function exportTeam(contestsFactory, ngDialog, APPCONFIG, formatDateFactory) {
var vm = this;
var directive = {
restrict: 'AE',
transclude: true,
controller : function($scope) {
$scope.test = function() {
alert('hey');
}
},
link : function(scope, element, attributes) {
element.on('click', function(e){
var ticket = attributes['exportTeam'];
var id_session = attributes['idsession'];
scope.openExportTeamDialog(ticket, id_session, false);
})
scope.openExportTeamDialog = function(ticket, aams_session_id, afterSave) {
ngDialog.open({
id : 'ft-modal-exportTeam-detail',
className : 'ngdialog ngdialog-theme-default ft-dialog-exportTeam',
controller: ['$scope', 'contestsFactory', 'APPCONFIG', function($scope, contestsFactory, APPCONFIG){
$scope.title = "Aggiungi contest compatibili";
$scope.bodyUrl = 'app/components/modals/body/exportTeam.html';
$scope.contentLoading = true;
$scope.currency = APPCONFIG.CURRENCY_SYMBOL;
$scope.afterSave = afterSave;
$scope.CompatibleContests = [];
contestsFactory.getCompatibleContests(ticket).then(function(response){
angular.forEach(response.data[0], function(item, i){
var multientryOptions = [];
if(item.multientry > 1) {
item.isMultientry = false;
var n = parseInt(item.multientry);
for (i = 1; i <= n; i++) {
multientryOptions.push({
text : i+" team",
value : i
})
}
item.multientryOptions = multientryOptions;
item.multientryOptionSelected = multientryOptions[0];
}else{
item.isMultientry = true;
};
})
$scope.CompatibleContests = response.data[0];
$scope.contentLoading = false;
})
}]
});
}
scope.openExportTeamDialog('N3E94100A725F9QG', 'M3E921013C6DCFCT', false);
}
};
return directive;
}
})();
The buy-ticket directive makes an http call, on the response i want to be able to call the onBuyTicket method of the <export> directive.
I'm trying to understand the best way to do that.
Thanks everyone
This sample show to you how can call an function from your directive
In this sample you can see we just insert data in our directive, and then we handle the data and other action in the directive.
var app = angular.module("app", []);
app.controller("ctrl", function ($scope) {
$scope.dataFromYourController = [
{ name: "Concert Jennifer", value: 200 },
{ name: "007", value: 100 }
];
})
.directive("export", function () {
var template = "<div>" +
"<ul>" +
"<li ng-repeat=\"array in arrays\">" +
"<button ng-click=\"onBuyTicket()\">buy Ticket {{array.name}}</button><hr>" +
"</li>" +
"</ul>" +
"</div>";
return {
restrict: "E",
template: template,
scope: {
data: "="
},
link: function (scope, elem, attrs, ngModel) {
scope.arrays = scope.data;
scope.onBuyTicket = function () {
alert("calling function from directive");
}
}
};
})
<!doctype html>
<html ng-app="app" ng-controller="ctrl">
<head>
</head>
<body>
<h1>call action from your directive</h1>
<export data="dataFromYourController"></export>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</body>
</html>
#eesdil
var directive = {
restrict: 'AE',
transclude: true,
controller : function($scope) {
$scope.onBuyTicket = function() {
alert('hey');
}
}
}
Ho can I call that from the buy-ticket directive ?
Use the $parent
<button buy-ticket="{{data}}" buy-callback="$parent.onBuyTicket()">buy</button>
So the expor directive something like:
var directive = {
restrict: 'AE',
template: '<ng-tansclude></ng-transclude>',
transclude: true,
controller : function($scope) {
$scope.onBuyTicket = function() {
alert('hey');
}
}
}
UPDATED:
see the plunker:
https://plnkr.co/edit/fmyJ4oPLvTiI0TzO7h1b?p=preview
It really depends what you can call and what you cannot based on the scopes... here if you would remove the scope from the export directive would work without the $parent also as export would share the same scope as the parent (main view)
The best way to communicate events from a child directive to a parent directive (or controller) is to use the $emit method of the scope.
What you want to do is take an ng-click event, get additional information with an $http call, and $emit an event with the additional information to be used by your parent directive (or controller).
HTML
<button buy-ticket="data" ng-click="onBuyTicket()">buy</button>
The directive:
angular.module("myApp").directive("buyTicket", function($http) {
function linkFn(scope,elem,attrs) {
scope.onBuyTicket = function() {
var buyData = scope.$eval(attrs.buyTicket);
var url = someFunction(buyData);
$http.get(url).then (function (response) {
var httpData = response.data;
scope.$emit("buyTicket.click", buyData, httpData);
});
};
};
return {
restrict: "AE",
link: linkFn
};
});
In the parent controller:
$scope.$on("buyTicket.click", function (buyData, httpData) {
console.log(buyData);
console.log(httpData);
});
Notice that I used the $eval method to get the data from the variable named by the buy-ticket attribute.
When choosing a name for the event, I recommend including the name of the directive in the event's name. It makes it clear the source of the event and is unlikely to be duplicated elsewhere.

Directive inside $modal window throws "undefined is not a function"

Using ui-bootstrap I have a really simple custom directive that lists alerts at the top of the page. On normal pages it works like a champ. When I use my directive inside a $modal popup I get "undefined is not a function" at ngRepeatAction.
The directive I have behind the modal on the main page still works. I can see it behind the modal. It's just the one in the modal popup that breaks. What am I doing wrong?
Modal open code:
$modal.open({
templateUrl: 'partials/main/servers/serverAuths/edit.html',
controller: function($scope, $modalInstance) {
$scope.auth = angular.copy(auth);
$scope.auth.password = null;
$scope.saveAuth = function() {
Auths.editAuth($scope.auth).then(
function(resp) {
if (resp.rc===0) {
Alerts.addAlert('success', 'Auth `'+$scope.auth.name+'` saved.');
_.extend(auth, $scope.auth);
$modalInstance.close();
} else {
Alerts.addAlert('danger', 'Auth `'+$scope.auth.name+'` could not be saved. ' + resp.message, 'serverAuths');
}
}
);
};
$scope.resetAuth = function() {
$modalInstance.close();
};
}
}).result.then(
function() {
Auths.getAuthList().then(
function(resp) {
$scope.auths = resp;
}
);
}
);
Directive template:
<div class="alert-wrapper alert-{{ alert.type }}"
ng-repeat="alert in alerts"
ng-class="{ 'relative':relative }">
<div class="container">
<div alert type="alert.type" close="closeAlert($index)">{{alert.msg}}</div>
</div>
</div>
Directive code:
angular.module('app')
.directive('appAlerts', function() {
return {
restrict: 'A',
replace: true,
scope: {
watchForm: '=',
relative: '#'
},
templateUrl: 'partials/directives/appAlerts.html',
controller: function($scope, Alerts) {
$scope.closeAlert = function(idx) { Alerts.closeAlert(idx); };
$scope.alerts = Alerts.getAlerts();
}
};
});
Alerts Factory:
angular.module('app').factory('Alerts', function($timeout) {
var alerts = [];
function timeoutAlert(a) {
$timeout(function() {
a.splice(0, 1);
}, 2500);
}
var addAlert = function(type, msg) {
alerts.push({type:type, msg:msg});
timeoutAlert(alerts);
};
var closeAlert = function(index) {
alerts.splice(index, 1);
};
var getAlerts = function() {
return alerts;
};
var killAlert = function(msg) {
var alert = _.where(alerts, {msg:msg});
var idx = _.indexOf(alerts, alert[0]);
if (idx > -1) {
closeAlert(idx);
}
};
return {
addAlert:addAlert,
closeAlert:closeAlert,
getAlerts:getAlerts,
killAlert:killAlert
};
});

Resources