Using AngularJS component props - angularjs

I'm new to angularJS, and now I'm trying to realize some parts.
The questions is: how do I get access to callback onFinish() which is passed to component "my-timer" and run it? this.onFinish() returns the error.
Here is my markup:
<div ng-app="app" ng-controller="MyCtrl as myCtrl">
<div>
Status: {{myCtrl.status ? myCtrl.status : 'Waiting...'}}
</div>
<div>
<button ng-click="myCtrl.addTimer(5)">Add timer</button>
</div>
<div ng-repeat="timer in myCtrl.timers">
<div>
<h3>Timer {{timer.id}}</h3>
<button ng-click="myCtrl.removeTimer($index)">X</button>
<my-timer id="{{timer.id}}" start-seconds="{{timer.seconds}}" on-finish="myCtrl.onFinish(endTime)"></my-timer>
</div>
</div>
</div>
And here is index.js
var app = angular.module('app', []);
app.controller('MyCtrl', class {
constructor($scope) {
this.status = null;
this.timerId = 0;
this.timers = [];
this.addTimer(10);
this.addTimer(3);
console.log($scope);
}
addTimer(seconds) {
this.timers.push({
id: this.timerId++,
seconds
});
}
removeTimer(index) {
this.timers.splice(index, 1);
}
onFinish(endTime){
this.status = `Timer finished at ${endTime}`;
console.log(endTime);
}
});
app.component('myTimer', {
bindings: {
id: '#',
startSeconds: '#',
onFinish: '&',
},
controller: function($interval, $scope) {
this.endTime = null;
this.$onInit = function() {
this.countDown();
};
this.countDown = function() {
$interval(() => {
this.startSeconds = ((this.startSeconds - 0.1) > 0) ? (this.startSeconds - 0.1).toFixed(2) : 0;
}, 100);
};
},
template: `<span>{{$ctrl.startSeconds}}</span>`,
});
And here is jsFiddle

this.$onInit = function() {
this.countDown();
};
this.onFinish('1');
The problem here is that you tried to execute this.onFinish right in controller's body. And that wont work that way. If you want this function to be called during initialization, move it to $onInit
this.$onInit = function() {
this.countDown();
this.onFinish('1');
};
Otherwise, call it from another component method. You can only declare variables and component methods in controller body, but not call functions.

Related

AngularJS 1.7.9 Binding variable from controller to component provide undefined value

i'm asking for your help.
I'm an AngularJS begginer and components bindings looks like esotherical for me.
I don't really understand why my variables a sets to undefined.
Could you help me please ?
Here is my HTML :
<div ng-controller="mapController as map" style="text-align:center;">
<div>{{map.lastClickedCountry}}</div>
<risk-map lastClickedCountry="map.lastClickedCountry"
countriesUnits="map.countriesUnits">
</risk-map>
</div>
My Component JS :
angular.module('riskApp').component("riskMap", {
bindings: {
lastClickedCountry: '=',
countriesUnits: '='
},
templateUrl: 'template/risk.html',
controller: function ($scope) {
this.$onInit = function () {
console.log("onInit")
console.log(this);
console.log($scope);
};
this.$onChanges = function () {
console.log("onChange")
console.log(this);
console.log($scope);
};
this.setArrivant = function (pays, nombreArrivant) {
this.countriesUnits[pays].arrivant = nombreArrivant
}
this.setStationnaire = function (pays, nombreStationnaire) {
this.countriesUnits[pays].stationnaire = nombreStationnaire
}
this.getArrivant = function (pays) {
return this.countriesUnits[pays].arrivant
}
this.getStationnaire = function (pays) {
return this.countriesUnits[pays].stationnaire
}
this.click = function (country) {
console.log("Dernier pays : " + this.lastClickedCountry)
console.log("Pays click : " + country)
console.log(this)
this.lastClickedCountry = country;
}
}
})
My Controller JS:
angular.module('riskApp').controller('mapController', function
CountCtrl($scope) {
this.lastClickedCountry = "test";
this.countriesUnits = {}
})
Attributes bindings need to be in kebab-case:
<div ng-controller="mapController as map" style="text-align:center;">
<div>{{map.lastClickedCountry}}</div>
<risk-map last-clicked-country="map.lastClickedCountry"
countries-units="map.countriesUnits">
</risk-map>
</div>
For more information, see
AngularJS Developer Guide - Directive Normalization

RxJs and Angular1 component - how to avoid $scope?

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

AngularJS binding issues and iteration loops

I have this factory:
.factory('Options', function () {
var getOptions = function () {
var storageData = sessionStorage.siteOptions;
if (storageData !== 'undefined')
return angular.fromJson(storageData);
return {
rotateBackground: false,
enableMetro: true
};
};
var saveOptions = function (options) {
sessionStorage.siteOptions = angular.toJson(options);
}
return {
get: getOptions,
save: saveOptions
};
});
which works fine on my profile page:
.controller('ProfileController', ['Options', function (options) {
var self = this;
self.options = options.get();
self.save = function () {
options.save(self.options);
}
}]);
The html looks like this:
<div class="row" ng-controller="ProfileController as profile">
<div class="large-4 columns">
<h2>Site options</h2>
<form name="optionsForm" ng-submit="profile.save()" role="form">
<div class="row">
<div class="large-12 columns">
<input id="enable-metro" type="checkbox" ng-model="profile.options.enableMetro"><label for="enable-metro">Enable metro design</label>
</div>
<div class="large-12 columns">
<input id="enable-background-rotate" type="checkbox" ng-model="profile.options.rotateBackground"><label for="enable-background-rotate">Enable rotating background</label>
</div>
<div class="large-12 columns">
<button class="button">Save</button>
</div>
</div>
</form>
</div>
</div>
But I have this other page that has a controller that needs to be aware if the options are ever saved. Basically, if saveOptions is ever called, then I need any page that looks at options to be notified.
The reason for this, is for example:
.controller('MetroController', ['Options', function (options) {
scope.options = options.get();
scope.$watch(function () {
return options.get();
}, function () {
scope.options = options.get();
});
}])
// ---
// DIRECTIVES.
// ---
.directive('metro', function () {
return {
restrict: 'A',
controller: 'MetroController',
controllerAs: 'metro',
link: function (scope, element, attr) {
scope.$watch(function () {
return metro.options.enableMetro;
}, function (enableMetro) {
if (enableMetro) {
element.addClass('metro');
} else {
element.removeClass('metro');
}
});
}
}
});
As you can see, this is trying to apply a class based on the enableMetro flag. But when I run this, I get an error about the amount of iterations this has had to loop through.
Can someone help me with this?
I think I have this solved.
I changed my options factory to this:
.factory('Options', function () {
var getOptions = function () {
var storageData = sessionStorage.siteOptions;
if (storageData !== 'undefined')
return angular.fromJson(storageData);
return {
rotateBackground: false,
enableMetro: true
};
};
var saveOptions = function (options) {
sessionStorage.siteOptions = angular.toJson(options);
current = getOptions();
}
var current = getOptions();
return {
current: current,
save: saveOptions
};
});
then in my controllers, I just did this:
.controller('MetroController', ['$scope', 'Options', function ($scope, options) {
var self = this;
self.options = options.current;
$scope.$watch(function () {
return options.current;
}, function () {
self.options = options.current;
});
}])
// ---
// DIRECTIVES.
// ---
.directive('metro', function () {
return {
restrict: 'A',
controller: 'MetroController',
link: function (scope, element, attr, controller) {
scope.$watch(function () {
return controller.options.enableMetro;
}, function (enableMetro) {
if (enableMetro) {
element.addClass('metro');
} else {
element.removeClass('metro');
}
});
}
}
});
and that seems to work fine.

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
};
});

Accessing a service or controller in my link function - Angular.js

I have a directive, but I am having a problem access the controller and my service that is injected into it. Here is my directive:
angular.module('clinicalApp').directive('chatContainer', ['encounterService', function(encounterService) {
return {
scope: {
encounter: '=',
count: '='
},
templateUrl: 'views/chat.container.html',
controller: 'EncounterCtrl',
link: function(scope, elem, attrs, controller) {
scope.addMessage = function(message) {
//RIGHT HERE
scope.resetChat();
};
scope.resetChat = function() {
scope.chatText = '';
scope.updateCount(scope.chatText);
};
}
};
}]);
You can see that I am attaching a couple of functions to my scope inside the link function. Inside those methods, like addMessage, I don't have access to my controller or the service that is injected into the directive. How do I acceess the controller or service?
UPDATE
Here is the service:
angular.module('clinicalApp').factory('encounterService', function ($resource, $rootScope) {
var EncounterService = $resource('http://localhost:port/v2/encounters/:encounterId', {encounterId:'#id', port: ':8280'}, {
search: {
method: 'GET'
}
});
var newEncounters = [];
var filterTerms = {};
EncounterService.pushNewEncounter = function(encounter) {
newEncounters.push(encounter);
$rootScope.$broadcast('newEncountersUpdated');
};
EncounterService.getNewEncounters = function() {
return newEncounters;
}
EncounterService.clearNewEncounters = function() {
newEncounters = [];
}
EncounterService.setFilterTerms = function(filterTermsObj) {
filterTerms = filterTermsObj;
$rootScope.$broadcast('filterTermsUpdated');
EncounterService.getFilterTerms(); //filter terms coming in here, must redo the search with them
}
EncounterService.getFilterTerms = function() {
return filterTerms;
}
return EncounterService;
});
and the chat.container.html
<div class="span4 chat-container">
<h5 class="chat-header">
<span class="patient-name-container">{{encounter.patient.firstName }} {{encounter.patient.lastName}}</span>
</h5>
<div class="chat-body">
<div class="message-post-container">
<form accept-charset="UTF-8" action="#" method="POST">
<div class="text-area-container">
<textarea id="chatBox" ng-model="chatText" ng-keyup="updateCount(chatText)" class="chat-box" rows="2"></textarea>
</div>
<div class="counter-container pull-right">
<span class="muted" id="counter">{{count}}</span>
</div>
<div class="button-container btn-group btn-group-chat">
<input id="comment" class="btn btn-primary btn-small btn-comment disabled" value="Comment" ng-click="addMessage(chatText)"/>
</div>
</form>
<div messages-container messages="encounter.comments">
</div>
</div>
</div>
</div>
Here is Demo Plunker I played with.
I removed scope{....} from directive and added 2 values in controller and directive to see how they change regards to action.
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
// listen on any change of chatText in directive
$scope.$watch(function () {return $scope.chatText;},
function (newValue, oldValue) {
if (newValue == oldValue) {return;}
$scope.chatTextFromController = newValue;
}, true);
});
app.directive('chatContainer', ['encounterService', function(encounterService) {
return {
templateUrl: 'chat.container.html',
link: function(scope, elem, attrs) {
scope.countStart = scope.count;
scope.updateCount = function(chatText) {
alert('updateCount');
scope.count = scope.countStart - chatText.length;
};
scope.addMessage = function(message) {
alert('addMessage');
encounterService.sayhello(message);
scope.resetChat();
};
scope.resetChat = function() {
alert('resetChat');
scope.chatText = 'someone reset me';
scope.name = "Hello " + scope.name;
scope.updateCount(scope.chatText);
};
}
};
}]);
app.service('encounterService', function() {
var EncounterService = {};
EncounterService.sayhello = function(message) {
alert("from Service " + message);
};
return EncounterService;
});
HTML
<body ng-controller="MainCtrl">
<div chat-container></div>
<pre>chatText from directive: {{chatText|json}}</pre>
<pre>chatText from controller: {{chatTextFromController|json}}</pre>
<pre>name: {{name|json}}</pre>
</body>

Resources