I have a global variable which is used to get information from server each 3 minutes if there are new messages. The variable name is HasNewMessage. Now what I want to do is to get the value of that variable in one of my AngularJS apps.
My directive is this
commonApp.directive('osMessageList', ['getMessages', '$location', '$anchorScroll', 'searchMessages', 'userfactory', '_', '$rootScope', '$window',
function (getMessages, $location, $anchorScroll, searchMessages, userfactory, _, $rootScope, $window) {
return {
restrict: 'A',
scope: {
messages: "="
},
controller: function ($scope) {
$scope.hasNewMessages = false;
$scope.$watch(function () {
return $scope.hasNewMessages;
}, function (newValue, oldValue) {
if (newValue) {
}
}, true);
},
function startInterval() {
// do something
// get scope of that directive and update the value
},SOME_TIME)
How can I acheive this?
If the startInterval function is outside the angularjs space (ie. global) it's a bit trickier. I assume there's a reason you can't simply use the $interval service and keep it all in the angularjs framework.
You could try using a native js event, and add a listener in your angularjs controller with reference to the services/scopes/etc in the callback of the listener.
https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
Create the event globally on page load or something. Dispatch it in your startInterval callback when the HasNewMessage state changes.
Add the listener in your directive controller, similar to the watch.
Related
Basically I have made custom directive to which i pass Url as string then inside the directive controller i'm calling http.get() method which creates the content inside this directive. What i want is to be able to change the value of annotation-url attribute which in return will change the content inside the directive because the new Url will return different JSON object. But it seems the directive is not getting refreshed when i change the annotationData from the controller.
HTML
<span ng-click="annotatonData = 'data/annotations1.json'">John</span>
<span ng-click="annotatonData = 'data/annotations2.json'">Marry</span>
<ng-annotate user-options="user" annotation-url="{{annotatonData}}"></ng-annotate>
Controller:
app.controller('ngAnnotationsController', ['$scope', '$http', function ($scope, $http) {
//InIt default http get
$scope.annotatonData = 'data/annotations1.json';
}]);
Directive:
app.directive('ngAnnotate', function () {
return {
templateUrl: 'templates/...',
restrict: 'E',
scope: {
annotationUrl: "#"
},
controller: ['$scope', '$http', function ($scope, $http) {
$http.get($scope.annotationUrl).then(function (response) {
$scope.data = response.data;
...
...
First, I suggest you change to
scope: {
annotationUrl: "="
}
the you can add a watcher to invoke $http whenever value is changed
$scope.$watch('annotationUrl', function(newVal, oldVal) {
if (newVal != oldVal) {
$http.get(newVal).then(function (response) {
$scope.data = response.data;
...
...
}
}
However, if you want to keep annotationUrl as it is, you need to use
$attr.$observe('annotationUrl', fn) to catch value changes.
*Note: the code was written with TypeScript and the code below is the compiled javascript - I can provide the TypeScript if necessary.
I am trying to minify a file called bundle.js using grunt-contrib-uglify (mangle: true, beautify: true) which contains all compiled TypeScript we need for our AngularJs application. When running the application I run into the error "Unknown provider: bProvider <- b" and have been able to find the offending directive.
The directive below did not have inline annotations so I updated the code to have them:
appDirectives.directive('emailShareModal', ['$rootScope', '$compile', '$timeout', 'httpService', 'shareFromEmailService', function ($rootScope, $compile, $timeout, httpService, shareFromEmailService) {
return {
scope: {
type: '#type',
id: "=id",
defaultBody: "=body",
title: '#title'
},
link: function ($scope, element, attr) { return new emailShareModalDirective($scope, $rootScope, element, attr, $compile, $timeout, httpService); },
controller: function ($scope) { return new experienceShareForm($rootScope, $scope, shareFromEmailService); }
};
}]);
Now if I remove the 'controller' attribute, the directive does not throw an exception, telling me the link attribute is configured properly. Here is the experienceShareForm object being instantiated by the controller attribute:
var experienceShareForm = (function () {
function experienceShareForm($rootScope, $scope, shareFromEmailService) {
var _this = this;
this.$rootScope = $rootScope;
this.$scope = $scope;
this.shareFromEmailService = shareFromEmailService;
}
return experienceShareForm;
})();
I am at a loss to what may be the problem with the code - does anyone see anything amiss? Thanks much in advance.
Your controller is trying to use DI but you are not specifying what is going to be injected. The minification is changing $scope to the variable b and the exception is saying that no bProvider can be found.
Change the following:
controller: function ($scope) { return new experienceShareForm($rootScope, $scope, shareFromEmailService); }
to the following:
controller: ['$scope', function ($scope) { return new experienceShareForm($rootScope, $scope, shareFromEmailService); }]
Note that you do not need to do this for the link function, because it is always called with the same objects. The controller function can be injected with different arguments, specified by you.
I've got an Angular view thusly:
<div ng-include="'components/navbar/navbar.html'" class="ui centered grid" id="navbar" onload="setDropdown()"></div>
<div class="sixteen wide centered column full-height ui grid" style="margin-top:160px">
<!-- other stuff -->
<import-elements></import-elements>
</div>
This is controlled by UI-Router, which is assigning the controller, just FYI.
The controller for this view looks like this:
angular.module('pcfApp')
.controller('ImportElementsCtrl', function($scope, $http, $location, $stateParams, $timeout, Framework, OfficialFramework) {
$scope.loadOfficialFrameworks();
// other stuff here
});
The <import-elements> directive, looks like this:
angular.module('pcfApp').directive('importElements', function($state, $stateParams, $timeout, $window, Framework, OfficialFramework) {
var link = function(scope, el, attrs) {
scope.loadOfficialFrameworks = function() {
OfficialFramework.query(function(data) {
scope.officialFrameworks = data;
$(".ui.dropdown").dropdown({
onChange: function(value, text, $item) {
loadSections($item.attr("data-id"));
}
});
window.setTimeout(function() {
$(".ui.dropdown").dropdown('set selected', data[0]._id);
}, 0);
});
}
return {
link: link,
replace: true,
templateUrl: "app/importElements/components/import_elements_component.html"
}
});
I was under the impression that I'd be able to call the directive's loadOfficialFrameworks() method from my controller in this way (since I'm not specifying isolate scope), but I'm getting a method undefined error on the controller. What am I missing here?
The problem is that your controller function runs before your link function runs, so loadOfficialFrameworks is not available yet when you try to call it.
Try this:
angular.module('pcfApp')
.controller('ImportElementsCtrl', function($scope, $http, $location, $stateParams, $timeout, Framework, OfficialFramework) {
//this will fail because loadOfficialFrameworks doesn't exist yet.
//$scope.loadOfficialFrameworks();
//wait until the directive's link function adds loadOfficialFrameworks to $scope
var disconnectWatch = $scope.$watch('loadOfficialFrameworks', function (loadOfficialFrameworks) {
if (loadOfficialFrameworks !== undefined) {
disconnectWatch();
//execute the function now that we know it has finally been added to scope
$scope.loadOfficialFrameworks();
}
});
});
Here's a fiddle with this example in action: http://jsfiddle.net/81bcofgy/
The directive scope and controller scope are two differents object
you should use in CTRL
$scope.$broadcast('loadOfficialFrameworks_event');
//And in the directive
scope.$on('loadOfficialFrameworks_event', function(){
scope.loadOfficialFrameworks();
})
So I want to create a directive that outputs a global message.
Requirements of directive...
This directive message can be updated from any controller.
When the message is updated from any controller so is the directive consequently the view.
Clear the Message after the view is destoryed
So far I am doing this by creating a directive and service that work together.
Problem is Im not able to update the view when the message is update from inside other controllers
If someone could direct me on how to proceed that would be swell.
What about using $rootScope and broadcasting?
app.directive("alertMsg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: true,
template: '{{msg}}', // this string is the html that will be placed inside the <alert-msg></alert-msg> tags.
link: function (scope, $element, attrs) {
scope.msg = MsgService.getAlertMsg(); //set msg to be available to the template above <alert-msg>{{msg}}</alert-msg>
scope.$on("$destroy", function(){ //when the <alert-msg> view is destroyed clear the alert message
MsgService.clearAlertMsg();
});
}
};
}]);
app.service('MsgService', function() {
this.alertMsg = '';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
this.clearAlertMsg = function(){
this.alertMsg = '';
};
});
app.controller('NewPlateController', ['urlConfig', '$scope', '$http', '$location', 'MsgService', '$routeParams', function(urlConfig, $scope, $http, $location, MsgService, $routeParams) {
$scope.plate = {license_plate: $routeParams.plate, state: 'default-state'};
// create new plate via json request
$scope.createPlate = function(){
$http.post(urlConfig.rootUrl+"/plates.js", $scope.plate).success(function(data) {
$scope.plateInfo = data;
MsgService.setAlertMsg('Plate Sucessfully Created'); //Need to update the directive to actual show this update
$location.path('/plate/'+$scope.plateInfo.plate_id);
// http error: display error messages
}).error(function(data,status,headers,config) {
$scope.errors = data;
$('#new-plate-errors').slideDown('fast');
});
};
}]);
Use $rootscope.$emit to send messages from your controllers (and even services) and use $rootScope.$on to receive them in your directive.
You must remove the listener on the directive's scope destruction or you will have a memory leak.
app.directive("alertMsg", ['$rootScope', function($rootScope) {
return {
restrict: "E",
scope: true,
template: '{{msg}}', // this string is the html that will be placed inside the <alert-msg></alert-msg> tags.
link: function (scope, $element, attrs) {
var _unregister; // store a reference to the message event listener so it can be destroyed.
_unregister = $rootScope.$on('message-event', function (event, message) {
scope.msg = message; // message can be value, an object, or an accessor function; whatever meets your needs.
});
scope.$on("$destroy", _unregister) //when the <alert-msg> view is destroyed remove the $rootScope event listener.
}
};
}]);
app.controller('NewPlateController', ['urlConfig', '$scope', '$http', '$location', '$rootScope', '$routeParams', function(urlConfig, $scope, $http, $location, $rootScope, $routeParams) {
$scope.plate = {license_plate: $routeParams.plate, state: 'default-state'};
// create new plate via json request
$scope.createPlate = function(){
$http.post(urlConfig.rootUrl+"/plates.js", $scope.plate).success(function(data) {
$scope.plateInfo = data;
$rootScope.$emit('message-event', 'Plate Sucessfully Created'); //use $emit, not $broadcast. Only $rootscope listeners are called.
scope.$on("$destroy", function() { // remove the message when the view is destroyed.
$rootScope.$emit('message-event', "");
});
$location.path('/plate/'+$scope.plateInfo.plate_id);
// http error: display error messages
}).error(function(data,status,headers,config) {
$scope.errors = data;
$('#new-plate-errors').slideDown('fast');
});
};
}]);
The message is not persisted outside of the directive so it will be removed when its scope is destroyed.
Edit: Adding a JSFiddle showing a working example: http://jsfiddle.net/kadm3zah/
Edit 2: I missed the requirement to remove the remove the message added when the view is destroyed. With this method you could add a second emit to the message on the NewPlateController scope's destruction with an empty string message.
This does not cover dynamically adding or removing the directive to the DOM. For that you could use a service to add and later remove the directive tag. This is how module's like ngToast and ui.boostrap's modal service work. Using one of them may be more appropriate for what you want to accomplish.
I am trying to put some default values in my directive with Isolate scope. Basically, I need to do some DOM manipulations using the scope object when my directive is bound. Below is my code:
Controller:
angular.module('ctrl').controller('TempCtrl', function($scope, $location, $window, $timeout, RestService, CommonSerivce) {
$scope.showAppEditWindow = function() {
//Binding the directive isolate scope objects with parent scope objects
$scope.asAppObj = $scope.appObj;
$scope.asAppSubs = $scope.appSubscriptions;
//Making Initial Settings
CommonSerivce.broadcastFunction('doDirectiveBroadcast', "");
};
Service:
angular.module('Services').factory('CommonSerivce', function ($rootScope) {
return {
broadcastFunction: function(listener, args) {
$rootScope.$broadcast(listener, args);
}
};
Directive:
angular.module('directives').directive('tempDirective', function() {
return {
restrict : 'E',
scope:{
appObj:'=asAppObj',
appSubs: '=asAppSubs'
},
link : function(scope, element, attrs) {},
controller : function ($scope,Services,CommonSerivce) {
//Broadcast Listener
$scope.$on('doDirectiveBroadcast', function (event, args) {
$scope.setDefaults();
});
$scope.setDefaults = function() {
//Setting Default Value
alert(JSON.stringify($scope.appSubs)); //Coming as undefined
};
},
templateUrl:"../template.html"
};
});
Custom Directive element:
<temp-directive as-app-obj="asAppObj" as-app-subs="asAppSubs" />
Now, the issue is that while trying to access the isolate scope in the default method inside directive, I aam getting an undefined value whereas the data is coming and is getting bound to the DOM. How can I access the isolate scope in the broadcast listener and modify the directive template HTML? Is there another wasy for handling this?
The problem is: at that time angular does not update its bindings yet.
You should not access your variables like this, try to use angular js binding mechanism to bind it to view (by using $watch for example). Binding to parent scope variables means you're passive, just listen for changes and update other variables or your view. That's how we should work with angular.
If you still need to access it. You could try a workaround using $timeout
$scope.setDefaults = function() {
$timeout(function () {
alert(JSON.stringify($scope.appSubs)); //Coming as undefined
},0);
};
DEMO
It's better to use $watch
angular.module('ctrl', []).controller('TempCtrl', function ($scope, $location, $rootScope) {
$scope.appSubscriptions = "Subscriptions";
$scope.appObj = "Objs";
$scope.showAppEditWindow = function () {
//Binding the directive isolate scope objects with parent scope objects
$scope.asAppObj = $scope.appObj;
$scope.asAppSubs = $scope.appSubscriptions;
};
});
angular.module('ctrl').directive('tempDirective', function () {
return {
restrict: 'E',
replace: true,
scope: {
appObj: '=asAppObj',
appSubs: '=asAppSubs'
},
link: function (scope, element, attrs) {
},
controller: function ($scope, $timeout) {
$scope.$watch("appSubs",function(newValue,OldValue,scope){
if (newValue){
alert(JSON.stringify(newValue));
}
});
},
template: "<div>{{appSubs}}</div>"
};
});
DEMO
By using $watch, you don't need to broadcast your event in this case.
Most likely the isolated scope variable is not available when the directive's controller first instantiates but probably its available when you need it for a following event such as: within a function bound to an ng-click
its just a race condition and the object doesn't arrive exactly when directive's controller loads