My application is using Angularjs at client side. I have five directive which are using same logic. Following are required details of my code
I have one pure javascript class as AppUtilities.js having following method defined
var AppUtilities = {
//Get the guid for given collection
getGUID: function (collectionName) {
var prefix;
var guid;
//get first two character
if (!Utilities.isEmpty(collectionName)) {
prefix = collectionName.substring(0, 2);
//Get timestamp
var timestamp = Utilities.getTimestampId();
//concate prefix to get the guid
guid = prefix + timestamp;
}
return guid;
}
};
I have five different directive in which I need to use "getGUID()" method to bind with template. Since template is only able to bind with scope function therefore I have defined scope method in all these five template as below
scope.getGUID = function (collectionName) {
return AppUtilities.getGUID(collectionName);
}
Then in all the five directive template, this method is bind as scope variable
<h4 class="guid" id="guid" data-ng-bind-template="{{getGUID('goal')}}"></h4>
How can I avoid declaring these method as scope variable and directly use as AppUtilities.getGUID(collectionName) in the HTML template?
There are multiple ways, but honestly, it seems like more effort than its worth, since you can just do:
scope.getGUID = AppUtilities.getGUID;
Of course, you could use $rootScope, but to me personally it feels wrong - I like when things are explicitly declared and do not magically appear.
Alternatively, if you only need to render the GUID in the UI, create a GUID directive. For example:
.directive("guid", function(){
return {
template: "<span>{{getGUID()}}</span>",
link: function(scope, element, attrs){
scope.getGUID = function(){
return AppUtilities.getGUID(attrs.guid || attrs.name);
};
}
}
});
and use as:
<h4 class="guid"><guid name="goal"></guid></h4>
Without manipulating the individual scopes or the root scope, you could simply define a filter which is usable in all templates. Note that, for all the good reasons, I'd still define and inject AppUtilities, even if it is a global, as it's own service.
app.filter('toGUID', ['AppUtilities', function (AppUtilities) {
return function (input) {
return AppUtilities.getGUID(input);
};
}]);
// <pre>{{ 'goal'|toGUID }}</pre>
app.service('AppUtilities', function () {
return AppUtilities;
});
(function (app, ng) {
'use strict';
app.filter('toGUID', ['AppUtilities', function (AppUtilities) {
return function (input) {
return AppUtilities.getGUID(input);
};
}]);
app.service('AppUtilities', function () {
return AppUtilities;
});
var Utilities = {
isEmpty: function (collectionName) {
return false;
},
getTimestampId: function () {
return '_something';
}
};
var AppUtilities = {
//Get the guid for given collection
getGUID: function (collectionName) {
var prefix;
var guid;
//get first two character
if (!Utilities.isEmpty(collectionName)) {
prefix = collectionName.substring(0, 2);
//Get timestamp
var timestamp = Utilities.getTimestampId();
//concat prefix to get the guid
guid = prefix + timestamp;
}
return guid;
}
};
})(angular.module('app', []), angular);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0-rc.2/angular.min.js"></script>
<div data-ng-app="app">
<pre>{{ 'goal'|toGUID }}</pre>
</div>
Hide it in a div:
<div id="hidden" data-ng-model="hidden" style="display:none"></div>
<script>
$('#hidden').html(AppUtilities.getGUID(collectionName));
</script>
Then, ng-bind to the div's contents:
<div id="realDiv" data-ng-bind-html="hidden"></div>
Related
I am trying to add edit functionality to my app. In one view, I have a button that brings you to the edit page.
<button ng-click="editMission(selectedMission.key)">Edit Mission</button>
The value selectedMission.key is used to determine what to initialize the edit page's form data with.
In the controller the function looks like this:
$scope.editMission = function(key){
$location.path('/edit');
}
On the edit page I have:
<div data-ng-init="editInit()">
And in my controller I have:
$scope.editInit = function(){
var query = myDataRef.orderByKey();
query.on("child_added", function(missionSnapshot){
if (missionSnapshot.key()==key){
...
}
});
}
How can I run the initialize function based on the key value from editMission. Should I use some getter/setter approach with a global key variable? I tried just placing the editInit code in editMission but the form data does not populate on view load.
Common practice is to use a service to share variables between views/controllers.
So in your case you would use the getter/setter approach as you suspected. I don't know what exactly you're trying to do, but the service in your case would look something like this:
app.factory('missionKeyService', function() {
var currentMission= {};
return {
setMissionKey: function(missionKey) {
currentMission.key = missionKey;
},
getMissionKey: function() {
return currentMission.key;
}
}
})
And in your controller1:
//include 'missionKeyService' in your controller function params
$scope.editMission = function(key) {
missionKeyService.setMissionKey(key);
$location.path('/edit');
}
And controller2:
//include 'missionKeyService' in your controller function params
$scope.editInit = function() {
var currentKey = missionKeyService.getMissionKey();
//do something with this key
...
}
I was trying to access a value returned by a directive in my controller. I tried with a service. It's not working; it seems like the updated directive's return value is not picked up by the controller.
Here's my code:
HTML
<html ng-app="myApp">
<body ng-controller="MainCtrl">
<input type="file" file-reader="fileContent" />
<div>{{fileContent}}</div>
</body>
</html>
Controller
myApp.controller('MainCtrl', function($scope,sharedTrialService) {
$scope.newValue = sharedTrialService.TrialInfo;
$scope.newData = $scope.newValue.data;
});
Directive
myApp.directive('fileReader', function(sharedTrialService) {
return {
scope: {
fileReader:"="
},
link: function(scope, element) {
$(element).on('change', function(changeEvent) {
var files = changeEvent.target.files;
if (files.length) {
var r = new FileReader();
r.onload = function(e) {
var contents = e.target.result.replace(/\r\n|\r/g,'\n');
scope.$apply(function () {
var lines=contents.split('\n');
scope.fileReader = lines;
sharedTrialService.TrialInfo.data=scope.fileReader;
});
};
r.readAsText(files[0]);
}
});
}
};
Service
myApp .factory('sharedTrialService', function () {
return {
TrialInfo: {
data: " "
}
};
})
});
How can I access the directive's sharedTrialService.TrialInfo.data from my controller?
With $scope.newValue = sharedTrialService.TrialInfo.data; you are just assign a string to newValue, but you need to assign a reference, so you need to assign an object or an array. So change it to $scope.newValue = sharedTrialService.TrialInfo; and access your data via newValue.data.
EDIT
Use $watch in your controller for debugging:
$scope.$watch('newValue', function() {
console.log('newValue:', $scope.newValue);
}, true);
$scope.$watch('newData', function() {
console.log('newData:', $scope.newData);
}, true);
I think, $scope.newValue should change as it's an object reference, but $scope.newData is just a string value and is the same as stated above, it won't change. So, you always need to access it via $scope.newValue.data.
From console
newValue: Object {data: " "}
newData:
newValue: Object {data: Array[6]}
$scope.newData won't update, because it has still just the reference to that string value.
In your service you define your string value " ", TrialInfo.data holds only a reference to that string value. With $scope.newData = $scope.newValue.data; you copy that reference to $scope.newData. In your directive you copy the reference of the filereader to sharedTrialService.TrialInfo.data, but $scope.newData has still the reference to that string value. $scope.newData and TrialInfo.data hold different references.
$scope.newValue holds a reference to the object TrialInfo. You can do everything with its properties, but the reference to that object remains unchanged. So, if the data property changed you can still access it via $scope.newValue.data.
Maybe I'm not the best to explain it.
I have ng-click method showSchedules() that calls AJAX:
$scope.showSchedules = function () {
$scope.loadCalendar = true;
appointmentService.getSchedule().then(function (response) {
calendarService.set(response.data.calendar, function () {
$timeout(function () {
$scope.refleshCalendars();
$scope.loadCalendar = false;
console.log($scope.events);
}, 100);
});
});
};
Inside this method you can see: console.log($scope.events);
It gives me filled array by objects.
When I do {{events}} in trmplate HTML I get [].
Why I get empty array?
HTML code:
<div ng-controller="ScheduleController as vmx">
{{events}}
<mwl-calendar
events="events"
view="vmo.calendarView"
view-title="vmo.calendarTitle"
current-day="vmo.calendarDay"
on-event-click="vmo.eventClicked(calendarEvent)"
on-event-times-changed="vmo.eventTimesChanged(calendarEvent);
calendarEvent.startsAt = calendarNewEventStart;
calendarEvent.endsAt = calendarNewEventEnd"
auto-open="true"
day-view-start="06:00"
day-view-end="23:00"
day-view-split="30"
cell-modifier="vmo.modifyCell(calendarCell)">
</mwl-calendar>
</div>
You want {{vmx.events}} because you are using the controller as annotation
Using controller as makes it obvious which controller you are
accessing in the template when multiple controllers apply to an
element.
ngController documentation (Search for controller as)
In AngularJS filters are used in view templates using this syntax:
{{ expression | filter }}
Expression can be a model defined in the controller:
$scope.expression = 45
How can I pass the built-in filter in the variable? This expression doesn't work:
$scope.filter = 'currency'
Edit: I am now using a slightly modified function from the answer.
Controller:
$scope.expression = 45;
$scope.filter = 'currency';
$scope.filterFn = function (value, filterName, filterArgs) {
if (filterName)
return $filter(filterName)(value, filterArgs);
return value;
}
View:
{{filterFn(expression, filter, args)}}
Another solution, if you want dynamic filter names, is creation of filter that would be a wrapper for others:
app.filter('filterWrapper', function ($filter) {
return function () {
var input = arguments[0],
name = arguments[1],
args = [].slice.call(arguments, 2); //dirty hack, arguments are not Array
return $filter(name)(input, args);
}
})
In view use as: {{greeting | filterWrapper:filterName }}.
Checkout fiddle for full example http://jsfiddle.net/Y9g4q/11/.
Instead of applying the filter in the view you could rather apply it directly in your controller like this:
function YourController ($scope, $filter) {
$scope.yourFilteredVar = $filter('currency')(45);
}
Alternativly you could save the filter function as a property of $scope and use it in the view:
function YourController ($scope, $filter) {
$scope.filterFn = $filter('currency')
$scope.yourVar =45;
}
And the View:
<span>My Var: {{filterFn(yourVar)}}</span>
I'm sure this is going to be a "dont do that!" but I am trying to display the style on an angular element.
<div ng-repeat="x in ['blue', 'green']" class="{{x}}">
<h3 insert-style>{{theStyle['background-color']}}</h3>
</div>
Result would be
<div class='blue'><h3>blue(psudeo code hex code)</h3></div>
<div class='green'><h3>green(psudeo code hex code)</h3></div>
I basically need to get the style attributes and display them.
Directive Code...
directives.insertStyle = [ function(){
return {
link: function(scope, element, attrs) {
scope.theStyle = window.getComputedStyle(element[0], null);
}
}
}];
Fiddle example: http://jsfiddle.net/ncapito/G33PE/
My final solution (using a single prop didn't work, but when I use the whole obj it works fine)...
Markup
<div insert-style class="box blue">
<h4 > {{ theStyle['color'] | toHex}} </h4>
</div>
Directive
directives.insertStyle = [ "$window", function($window){
return {
link: function(scope, element, attrs) {
var elementStyleMap = $window.getComputedStyle(element[0], null);
scope.theStyle = elementStyleMap
}
}
}];
Eureka!
http://jsfiddle.net/G33PE/5/
var leanwxApp = angular.module('LeanwxApp', [], function () {});
var controllers = {};
var directives = {};
directives.insertStyle = [ function(){
return {
link: function(scope, element, attrs) {
scope.theStyle = window.getComputedStyle(element[0].parentElement, null)
}
}
}];
leanwxApp.controller(controllers);
leanwxApp.directive(directives);
So that just took lots of persistence and guessing. Perhaps the timeout is unnecessary but while debugging it seemed I only got the style value from the parent after the timeout occurred.
Also I'm not sure why but I had to go up to the parentElement to get the style (even though it would realistically be inherited (shrug)?)
Updated fiddle again
Did one without the timeout but just looking at the parentElement for the style and it seems to still work, so scratch the suspicions about the style not being available at all, it's just not available where I would expect it.
Also holy cow there are a lot of ways to debug in Chrome:
https://developers.google.com/chrome-developer-tools/docs/javascript-debugging
I used
debugger;
statements in the code to drop in breakpoints without having to search all the fiddle files.
One more quick update
The code below comes out of Boostrap-UI from the AngularUI team and claims to provide a means to watch the appropriate events (haven't tried this but it looks like it should help).
http://angular-ui.github.io/bootstrap/
/**
* $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
* #param {DOMElement} element The DOMElement that will be animated.
* #param {string|object|function} trigger The thing that will cause the transition to start:
* - As a string, it represents the css class to be added to the element.
* - As an object, it represents a hash of style attributes to be applied to the element.
* - As a function, it represents a function to be called that will cause the transition to occur.
* #return {Promise} A promise that is resolved when the transition finishes.
*/
.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
var $transition = function(element, trigger, options) {
options = options || {};
var deferred = $q.defer();
var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
var transitionEndHandler = function(event) {
$rootScope.$apply(function() {
element.unbind(endEventName, transitionEndHandler);
deferred.resolve(element);
});
};
if (endEventName) {
element.bind(endEventName, transitionEndHandler);
}
// Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
$timeout(function() {
if ( angular.isString(trigger) ) {
element.addClass(trigger);
} else if ( angular.isFunction(trigger) ) {
trigger(element);
} else if ( angular.isObject(trigger) ) {
element.css(trigger);
}
//If browser does not support transitions, instantly resolve
if ( !endEventName ) {
deferred.resolve(element);
}
});
// Add our custom cancel function to the promise that is returned
// We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
// i.e. it will therefore never raise a transitionEnd event for that transition
deferred.promise.cancel = function() {
if ( endEventName ) {
element.unbind(endEventName, transitionEndHandler);
}
deferred.reject('Transition cancelled');
};
return deferred.promise;
};
// Work out the name of the transitionEnd event
var transElement = document.createElement('trans');
var transitionEndEventNames = {
'WebkitTransition': 'webkitTransitionEnd',
'MozTransition': 'transitionend',
'OTransition': 'oTransitionEnd',
'transition': 'transitionend'
};
var animationEndEventNames = {
'WebkitTransition': 'webkitAnimationEnd',
'MozTransition': 'animationend',
'OTransition': 'oAnimationEnd',
'transition': 'animationend'
};
function findEndEventName(endEventNames) {
for (var name in endEventNames){
if (transElement.style[name] !== undefined) {
return endEventNames[name];
}
}
}
$transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
$transition.animationEndEventName = findEndEventName(animationEndEventNames);
return $transition;
}]);
The issue you'll face is that getComputedStyle is considered a very slow running method, so you will run into performance issues if using that, especially if you want angularjs to update the view whenever getComputedStyle changes.
Also, getComputedStyle will resolve every single style declaration possible, which i think will not be very useful. So i think a method to reduce the number of possible style is needed.
Definitely consider this an anti-pattern, but if you still insist in this foolishness:
module.directive('getStyleProperty', function($window){
return {
//Child scope so properties are not leaked to parent
scope : true,
link : function(scope, element, attr){
//A map of styles you are interested in
var styleProperties = ['text', 'border'];
scope.$watch(function(){
//A watch function to get the styles
//Since this runs every single time there is an angularjs loop, this would not be a very performant way to do this
var obj = {};
var computedStyle = $window.getComputedStyle(element[0]);
angular.forEach(styleProperties, function(value){
obj[value] = computedStyle.getPropertyValue(value);
});
return obj;
}, function(newValue){
scope.theStyle = newValue;
});
}
}
});
This solution works if you don't HAVE to have the directive on the child element. If you just place the declaration on the ng-repeat element itself, your solution works:
<div insert-style ng-repeat="x in ['blue', 'green']" class="{{x}}">
Fiddle