I have a controller which is charge of getting event json data and if there is data, update the dom with data, else update dom with error message:
//Controller.js
myApp.controller('EventsCtrl', ['$scope','API', function ($scope, api) {
var events = api.getEvents(); //events: {data: [], error: {message: 'Some message'}}
}]);
//Directives.js
myApp.directive('notification', function () {
return {
restrict: 'A',
link: notificationLink
};
});
/**
* Creates notification with given message
*/
var notificationLink = function($scope, element, attrs) {
$scope.$watch('notification', function(message) {
element.children('#message').text(message);
element.slideDown('slow');
element.children('.close').bind('click', function(e) {
e.preventDefault();
element.slideUp('slow', function () {
element.children('#message').empty();
});
});
});
};
//Services.js
...
$http.get(rest.getEventsUrl()).success(function (data) {
// Do something with data
}).error(function (data) {
$window.notification = data;
});
Issue is that the element changes are triggered but $window.notification has nothing in it.
Edit: Attempted to try with $watch.
Edit: After moving both sets of html to one controller, the DOM manipulation works with $watch(). Thanks to both you of you for your help!
Try setting the result of your http request to a scope variable in your controller. Then watch that variable in your directive.
myApp.controller('EventsCtrl', ['$scope', 'API',
function ($scope, api) {
$scope.events = api.getEvents(); //events: {data: [], error: {message: 'Some message'}}
}
]);
//Directives.js
myApp.directive('notification', function () {
return {
restrict: 'A',
link: notificationLink
};
});
var notificationLink = function (scope, element, attrs) {
scope.$watch('events', function (newValue, oldValue) {
if (newValue !== oldValue) {
if (scope.events.data.length) {
//Display Data
} else {
//Display Error
}
}
});
};
Related
I'm trying to pass data from a controller to a directive using $broadcast and $on.
Data only appears on the directive's HTML template when I refresh the page. It does not appear when I route to the controller template with the embedded directive.
The weird thing is, the data appears to have been received when I console log. I have tried using $timeout and angular.element(document).ready
Controller:
$http.getDashboardData(...).success(function(res) {
populateResults(res);
...
}
function populateResults (data) {
$rootScope.safeApply(function () {
$rootScope.$broadcast('show-results', data);
});
}
Directive:
.directive('results',['$rootScope', function ($rootScope) {
return {
restrict: 'AE',
scope: {},
transclude: true,
templateUrl: '/html/directives/results.html',
link: function(scope, elem, attr){
...
$rootScope.$on('show-results', function(event, args) {
angular.element(document).ready(function () {
scope.init(args);
});
});
scope.init = function (args) {
console.log('ARGS', args); //Has data
scope.questions = args;
};
Controller Page with embedded results directive:
<div class="myPage">
<results></results>
</div>
Directive HTML:
<div>
QUESTIONS: {{questions}} : //Empty array
</div>
Console Log: You can see it has data:
Routing sequence:
.config:
...
state('dashboard', {
url : '/dashboard',
templateUrl: '/html/pages/dashboard.html',
controller: 'dashboardCtrl',
resolve : {
ProfileLoaded : function ($rootScope) {
return $rootScope.loadProfile();
}
}
});
.run: This is to load profile if user refreshes the page:
$rootScope.loadProfile = function () {
return Me.getProfile({user_id : Cookies.get('user_id')}).success(function (res) {
$rootScope.me = res;
}).error(function (err) {
console.log('Error: ', err);
});
};
In your directive link function, try to use scope.$on instead of $rootScope.$on, and use scope.init directly without document.ready
I've yet to find an answer on SO, blogs or even Angular issues. I've got other services and controllers that use $http and $resource and their unit tests use $httpBackend.when() to mock the API and $httpBackend.flush() to return a response.
I'm having no luck with this directive, though.
I've created an asynchronous validator, based around the sample code in AngularJS docs. I have verified that the $http.get() is getting called before $httpBackend.flush() with console.log(). However, I get "No pending request to flush" for $httpBackend.flush(). Incidentally, I have also tried $httpBackend.expectGET(), and that expectation fails.
Directive code
'use strict';
angular.module('myApp')
.directive('myServerValidate', function ($q, $http) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attributes, ngModel) {
ngModel.$asyncValidators.server = function (modelValue, viewValue) {
return $http.get('http://myserver/api/System/ValidateField', {
params: {
field: scope.validationConfig.field,
value: '' + (modelValue || viewValue)
}
}).
then(function (successData) {
if (angular.isEmpty(successData) || angular.isEmpty(successData.Status)) {
return $q.reject();
} else if (!successData.Status) {
return $q.reject(successData.Message || '');
} else if (successData.Status) {
return $q.resolve(successData.Message || '');
}
}, function (rejectionData) {
return $q.reject(rejectionData);
});
};
}
};
});
Unit test
'use strict';
describe('Directive: myServerValidate', function () {
beforeEach(module('myApp'));
var form,
scope,
$httpBackend;
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
beforeEach(inject(function ($rootScope, $injector, $compile) {
scope = $rootScope.$new();
$httpBackend = $injector.get('$httpBackend');
$httpBackend.
when('GET', /ValidateField/).
respond(function () {
if (form.num === '1') {
return [200, {Status: true, Message: ''}, {}];
} else if (form.num === '2') {
return [200, {Status: false, Message: 'Invalid'}, {}];
} else {
return [404, {}, {}];
}
});
var element = angular.element('<form name="form">' +
'<input type="text" name="num" ng-model="model.num" hc-server-validate>' +
'</form>');
scope.model = {
num: null
};
scope.validationConfig = {
field: 'num'
};
$compile(element)(scope);
form = scope.form;
}));
it('should validate numeric value against server API', function () {
form.num.$setViewValue('1');
scope.$digest();
$httpBackend.flush();
expect(scope.model.num).toEqual('1');
expect(form.num.$valid).toBeTruthy();
});
});
I expected the scope.$digest() to kick off the validation (which it does), which would then call $http.get() (which happens), but $httpBackend somehow doesn't detect the call, or possibly $http doesn't generate the GET request.
I have a custom directive to confirm if a user that clicks an element really wants to perform an action:
.directive('ngReallyClick', ['$modal',
function ($modal) {
return {
restrict: 'A',
scope: {
ngReallyClick: "&",
},
link: function (scope, element, attrs) {
var isDeleteDisabled = scope.ngReallyDisabled;
element.bind('click', function () {
if (isDeleteDisabled != true) {
var message = attrs.ngReallyMessage || "Are you sure ?";
...
var modalInstance = $modal.open({
template: modalHtml,
controller: ModalInstanceCtrl
});
modalInstance.result.then(function () {
scope.ngReallyClick({ ngReallyItem: scope.ngReallyItem }); //raise an error : $digest already in progress
}, function () {
//Modal dismissed
return false;
});
};
});
}
}
}]);
It is used e.g:
<a ng-really-message="Are you sure you want to save and close?" ng-really-click="saveAndCloseGlobal(456)"
where saveAndCloseGlobal is called when the user confirms their choice. But, if I try and pass $event to this function, to get the original click event, it ends up undefined. If I use a plain ng-click=saveAndCloseGlobal($event) then I get the correct event object in saveAndCloseGlobal.
//My controller code
//I am getting values from database into angular js ckeditor but html tags are not applying in editor instead of it is showing html element tag code in ediator
myApp.directive('ckEditor', [function () {
return {
require: '?ngModel',
link: function ($scope, elm, attr, ngModel) {
var ck = CKEDITOR.replace(elm[0]);
ck.on('pasteState', function () {
$scope.$apply(function () {
ngModel.$setViewValue(ck.getData());
});
});
ngModel.$render = function () {
ck.setData(ngModel.$modelValue);
};
}
};
}])
myApp.controller('editcontentcontroller', function ($scope, $http)
{
$scope.ckEditors;
$http({ method: 'POST', url: 'pageedit.php' }).success(function (data)
{
// response data
$scope.id = data[0]['id'];
$scope.page = data[0]['page'];
$scope.ckEditors = data[0]['pagecontent'];
}).
error(function (data) {
console.log(data);
});
});
add following into your directive and check the CKEDITOR docs
ck.on('instanceReady', function () {
ck.setData(ngModel.$viewValue);
});
CKEDITOR.instanceReady Fired when a CKEDITOR instance is created, fully initialized and ready for interaction.
myApp.directive('ckEditor', function () {
return {
require: 'ngModel',
priority: 10,
link: function (scope, elm, attr, ngModel) {
var ck = CKEDITOR.replace(elm[0]);
if (!ngModel) return;
ck.on('instanceReady', function () {
ck.setData(ngModel.$viewValue);
});
function updateModel() {
scope.$apply(function () {
if (ck.getData().length) {
ngModel.$setViewValue(ck.getData());
}
});
}
ck.on('change', updateModel);
ck.on('key', updateModel);
ck.on('dataReady', updateModel);
ck.on('pasteState', updateModel);
ngModel.$render = function (value) {
ck.setData(ngModel.$viewValue || '');
};
}
};
})
I'm having trouble retrieving the value of an attribute passed in to a directive. My understanding of how directives work probably has something to do with this but it's my assumption that this is a scope related issue.
If you check the code below you'll see the attribute is being used as such:
display="contest.StyleBgImageMedia.filename"
This value of contest.StyleBgImageMedia.filename is a string and I've verified it exists by consoling it our from the controller. The problem is that when trying to access it within the directives link function I can't retrieve the value but only the attribute name.
This directive is used in the view like such:
<uploader class="pull-left" action="builder/uploadMediaFile" display="contest.StyleBgImageMedia.filename" data-file="style_bg_image_media_id"></uploader>
The full directive has been posted below. You'll see that I'm using $observe to render the value of the display attribute but this isn't working.
app.directive('uploader', function($rootScope) {
return {
restrict: 'E',
scope: {
action: '#',
display: '#display'
},
link: function(scope, elem, attrs) {
elem.find('.fake-uploader').click(function() {
elem.find('input[type="file"]').click();
});
scope.progress = 0;
attrs.$observe('display', function(value) {
if (value) {
scope.avatar = value;
}
});
scope.sendFile = function(el) {
var $form = jQuery(el).parents('form');
if (jQuery(el).val() === '') {
return false;
}
$form.attr('action', scope.action);
scope.$apply(function() {
scope.progress = 0;
});
$form.ajaxSubmit({
type: 'POST',
uploadProgress: function(event, position, total, percentComplete) {
scope.$apply(function() {
scope.progress = percentComplete;
});
},
error: function(event, statusText, responseText, form) {
$form.removeAttr('action');
},
success: function(responseText, statusText, xhr, form) {
var ar = jQuery(el).val().split('\\'),
filename = ar[ar.length-1];
$form.removeAttr('action');
scope.$apply(function() {
scope.avatar = filename;
});
var response = jQuery.parseJSON(responseText);
$rootScope.$broadcast('file-uploaded', {
'model': attrs.file,
'file': response.message
});
}
});
};
},
replace: false,
templateUrl: 'builder/views/partials/upload.php'
};
});
$observe doesn't work unless the attribute value contains interpolation.
So, you can change the attribute to be:
<uploader display="{{contest.StyleBgImageMedia.filename}}" ...>
Alternatively, you can use watch: scope.$watch('display', ...) with this isolated scope binding:
display: '='