AngularJS: Directive isolate scope - angularjs

I am trying to build a chat with Pusher and AngularJs.
<div id="chats" ng-controller="ChatCtrl">
<chat chat-id="1" chat-name="Max"></chat>
<chat chat-id="2" chat-name="John"></chat>
<chat chat-id="3" chat-name="Susanne"></chat>
</div>
I have the following directive:
.directive('chat', function() {
return {
restrict: 'E',
templateUrl: '/tpl/chat-box.html'
}
}]);
and the following controller:
.controller('ChatCtrl', ['$scope', '$pusher',
function($scope, $pusher) {
$scope.pusher = $pusher(client);
}])
Where should I handle ajax requests to receive old messages and Pusher channel binding to receive new messages for a chat-box? In the controller or in the directive?

Probably in a service, which would encapsulate other business logic as well. The controller would then just the service's API to send/receive data and make this data available to the view, while the directive should generally be only concerned with DOM manipulation, and it, too, should not contain business logic.
Your question is a bit too general though, and the title does not really reflect your question (your directive doesn't even have an isolate scope).

Related

Angular Controller and controllerAs keyword usage in directive

learning angular so some time things not clear when read article on angular. here i stuck to understand what is the usage or importance of this keywords Controller and controllerAs in directive.
code taken from here http://blog.thoughtram.io/angularjs/2015/01/02/exploring-angular-1.3-bindToController.html
app.controller('SomeController', function () {
this.foo = 'bar';
});
app.directive('someDirective', function () {
return {
restrict: 'A',
controller: 'SomeController',
controllerAs: 'ctrl',
template: '{{ctrl.foo}}'
};
});
i like to know understand the importance of this two keywords in directive and they are controller: 'SomeController', and controllerAs: 'ctrl',
please tell me if we do not use these two keyword controller: 'SomeController', and controllerAs: 'ctrl', then what would happen or what would be worse ?
please help me to understand the usage or importance of this keywords controller: 'SomeController', and controllerAs: 'ctrl', in directive. thanks
You need the controller if you plan on referencing a controller object. This is how you hook it up.
The controllerAs allows you to create a variable that you can reference the controller with in lieu of using the $scope.
Refined answer:
<html ng-app="app">
<head></head>
<body>
<script src="node_modules/angular/angular.js"></script>
<script>
var app = angular.module('app', []);
app.directive('fooDirective', function() {
return {
restrict: 'A',
controller: function($scope) {
// No 'controllerAs' is defined, so we need another way
// to expose this controller's API.
// We can use $scope instead.
$scope.foo = 'Hello from foo';
},
template: '{{foo}}'
};
});
app.directive('barDirective', function() {
return {
restrict: 'A',
controller: function() {
// We define a 'vm' variable and set it to this instance.
// Note, the name 'vm' is not important here. It's not public outside this controller.
// The fact that the 'controllerAs' is also called 'vm' is just a coincidence/convention.
// You could simply use 'this.bar' if you prefer.
var vm = this;
vm.bar = 'Hello from bar';
},
// This allows us to reference objects on the controller's instance by
// a variable called 'vm'.
controllerAs: 'vm',
// Now we can reference objects on the controller using the 'controllerAs' 'vm' variable.
template: '{{vm.bar}}'
};
});
</script>
<div foo-directive></div>
<div bar-directive></div>
</body>
</html>
One of its main advantages, especially if you're new to AngularJS, is that it ensures proper data binding between child scopes.
Just play around with this code sample and try to notice something strange:
angular
.module('myApp', [])
.controller('MainCtrl', ['$scope',
function($scope) {
$scope.truthyValue = true;
$scope.foo = 'hello';
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.3/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainCtrl">
<p>Start by writing something in the first input. Then write something in the second one. Good job, you've broke AngularJS!</p>
1.
<input type="text" ng-model="foo">
<div ng-if="truthyValue">
2.
<input type="text" ng-model="foo">
</div>
<div>$scope.foo: {{ foo }}</div>
</div>
The reason behind it is that ngIf creates a child scope which inherits from the parent scope. You're basically changing the value inside ngIf's scope which doesn't affect the value from its parent scope.
Finally, I consider controllerAs syntax an important AngularJS best practice. If you get accustomed to it early in your learning process, you'd be avoiding a lot of head-scratching wondering why your code doesn't work, especially when everything seems in order.
You don't need to use both controller and controllerAs. You can use the shorthand:
controller: 'SomeController as ctrl'
The relationship is that a new instance of the controller is created and exposed to the template using the instance handle you provide as ctrl.
Where this comes in handy is if you are using nested controllers -- or using multiple instances of a controller in a view.
UPDATE TO ANSWER COMMENTS
You do not need to use controllers with AngularJS directives. Infact as of AngularJS 1.5 you should probably only use controllers when creating components rather than directives.
Directives and Components are conceptually similar. Up until AngularJS they all components would be defined as a directive.
In many ways a directive interacts with an element (like ng-href) or events (like ng-click).
The simplest way to differentiate Components and Directives is a Component will have a template.
Can't I just create a component using the directive link method?
You can, but I wouldn't recommend it unless you have a good reason. Using controllers allows you to use object oriented classes or prototypes to define the action behaviors with the template and user.
As well these controllers are extremely more easy to unit test than the directive link functions.
Check out this plunkr code
Here is my simple Directive code:
angular.module('app', [])
.directive('someDirective', function () {
return {
scope: {},
controller: function ($scope) {
this.name = 'Pascal';
$scope.color = 'blue';
},
controllerAs: 'ctrl',
template: '<div>name: {{ctrl.name}} and Color: {{color}}</div>'
};
});
And The HTML
<body ng-app="app">
<some-directive />
</body>
So, as you can see, if you need to access some variable which were defined against this keyword in the controller, you have to use controllerAs. But if it was defined against $scope object you can just access it with its name.
For example, you can get the variable color just by using {{color}} as it was defined against $scope but you have to use {{ctrl.name}} as "name" was defined against this.
I don't think there really is much difference, as this answer says,
Some people don't like the $scope syntax (don't ask me why). They say
that they could just use this
Also from their own website you can read the about the motivation behind this design choice,
Using controller as makes it obvious which controller you are
accessing in the template when multiple controllers apply to an
element
Hope it helps.

Angular Manager Service

I have a page.html with a directive in it. I also have a service which I want to use as a manager of events between the directive and the page.html controller.
I'm new to Angular so I don't know if I'm approaching this the right way. I want the service to update the directive if say my controller has a button clicked. Do I just put the directive as a dependency for my service?
No. You have to inject your service in your directive.
The service should handle communication with your server or strictly business logic, best you must separate into two services if you have communication and business logic.
Directive is only for the visualization part.
Events should be placed into directive "link" function and visualization functionality into "controller" but remember. Angular is not jQuery so many events like "click" are obsolete in angular by that i mean they handled automatically by a property in the controller.
This sounds like a fine approach and is often necessary, since services are singletons and controllers and directives are not. You will inject your service into both your directive AND the page controller. This way both can act on the service and the service can act as a bridge between the two.
app.service("myService", function() {
//define your service
});
app.directive('myDirective', function (myMervice) {
return {
template: '...',
restrict: '...',
scope:true,
controller: function($scope) {
//you can use the service here
}
};
});
app.controller("myController", function($scope, myMervice) {
//you can use the service here
});

Is it possible to get access to a controller without declaring it?

I have a bit of a weird situation which requires me to pass my controllers to my directives via a directive scope variable. This works great as long as there are only one controller in use per route, which is declared in my $routeProvider.
But now I have to have 2 controllers in use in the same template, which causes problems because I can't declare my controllers using ng-controller because that will throw a routeProvider error since I'm trying to access data from my route resolve. (You can only access route resolve data if you declare the controller in the same route as the resolve, which then makes using ng-controller in the template and controller in the directive useless to me).
So this is what I want to do:
// Declare one controller in the routing
.when('/someroute', {
controller: 'MyCtrl'
}
// But pass a different controller to my directive that hasn't been declared
// in either the route, template or directive
<my-directive ctrl="MyOtherCtrl"></my-directive>
But my question is, is it possible to access a controller and its functions without declaring the controller as ng-controller, controller in directive or in route? My far-fetched idea is that there's a service or something that you can inject which holds all of the controllers, but so far I've come up with none.
You could inject th controller by name into your directives controller using $controller.
var someOtherController = $controller('SomeOtherController ',{$scope: $scope});
It's methods would now be available on $scope. Be careful with this though, things can get hairy quickly.
There is a service to get an instance of any controller, use the $controller
in your directive, inject the $controller service then use it in your link function:
myApp.directive("myDirective", function($controller){
return {
scope: {
ctrl: "#"
},
link: function(scope, element, attrs){
var myNeededCtrl = $controller(scope.ctrl, {$scope: scope, otherDepenciesThatTheControllerNeed: ...});
myNeededCtrl.doSomething();
}
};
});

Angular: injecting state into controllers (or binding "models" to controllers)

Suppose I have a general purpose controller, TableController, that can be used in multiple places in the app to display a table of Key x Value pairs via a custom directive, ui-table, that generates an HTML table.
angular.module('ui.table', [])
.controller('TableController', ['$scope', 'data',
function($scope, data) { $scope.data = data; }])
.directive('uiTable', function() {
return { templateUrl: 'table.html' }
});
I could use the controller in the following template:
<div ng:controller="TableController">
<div ui-table></div>
</div>
And create a factory to pass data to this controller.
.factory('data', function() {
return [{'App':'Demo', 'Version':'0.0.1'}];
})
But, I have multiple controllers (sometimes in the same views), so I need to "bind" a particular factory to a particular controller (e.g., UserProfile, AppData, etc.)
I have started to look at angular-ui-router's $stateProvider, but it seems too complicated for what must be a typical use case? What I'd really like to be able to do is use the template to annotate which factory (what I think of as a model) should be used for that particular controller. E.g., something like:
<div ng:controller="TableController" ng:model="AppData"></div>
What is the right approach?
EDIT:
I've figured out $stateProvider and "resolve" allows me to map provider services onto injected values for the state's main controller -- but the controller I want to influence is a child of this controller.
$stateProvider
.state('home', {
url: '/home',
templateUrl: '/home/view.html',
controller: 'MainViewController',
resolve: {
'data': 'AppData'
}
});
So, I still can't figure out how to influence the controllers inside the state's view.
I think what you are looking for is simply passing your data into the directive through attributes. Then use an isolated scope in directive so you can have multiple instances active at the same time
<div ng-controller="ViewController">
<div ui-table dataSource="tableData"></div>
</div>
Then your directive would be written in a generic way to be re-usable regardless of the data passed in.
.factory('SomeService', function(){
var data ={
headings: ['ID','Age','Name'],
rows:[
{id:1, 33,'Bill'}......
]
};
return {
get:function(){ return data;}
};
})
.controller('ViewController', function($scope, SomeService){
$scope.tableData = SomeService.get();
})
.directive.directive('uiTable', function () {
return {
scope: {
dataSource: '=' // isolated scope, 2 way binding
}
templateUrl: 'table.html',
controller: 'TableController', // this controller can be injected in children directives using `require`
}
});
In essence this is just reversing your layout of controller/directive. Instead of TableController wrapping the directive, it is used internally within directive. The only reason it is a controller in the directive is to allow it to be require'd by child directives such as perhaps row directive or headings directive and even cell directive. Otherwise if not needing to expose it for injection you can use link and put all sorts of table specific operations in there
As mentioned in my comments there are various approaches to creating a table directive. One is with heavy configuration objects, the other is with a lots of declarative view html that use many child directives. I would suggest analyzing the source of several different grid/table modules to see what best suits your coding style
Thanks in part to #charlietfl (above) I have an answer:
<ui-graph model="SomeGraphModel"></ui-graph>
Then:
angular.module('ui.graph', [])
.directive('uiGraph', [function() {
return {
controller: 'GraphController',
scope: {
model: '#model' // Bind the element's attribute to the scope.
}
}
}]);
.controller('GraphController', ['$injector', '$scope', '$element',
function($injector, $scope, $element) {
// Use the directive's attribute to inject the model.
var model = $scope.model && $injector.get($scope.model);
$scope.graph = new GraphControl($element).setModel(model);
}])
Then somewhere else in the app (i.e., not necessarily in the directive/controller's module):
angular.module('main', [])
.factory('SomeGraphModel', function() {
return new GraphModel();
})

Can you emit event from Controller in one module to Directive Controller in another module?

I have a controller "MyController" and a directive "MyDirective" in separate modules "app" and "directiveModule" respectively. DirectiveModule has been injected into the angular.module of "app".
The issue I am having is as part of "app", I have a controller that emits an event "TEST" that the controller for the directive does not pick up. How can I successfully get the directive of its own module to catch the emit? Is this possible? (Note: I tried $scope originally, then used $rootScope, but both do not make a difference.)
My controller:
app.controller('MyController', function($scope, $rootScope) {
$rootScope.$emit('TEST');
});
My directive:
directiveModule.directive('MyDirective', ['$rootScope', 'MyService',
function($rootScope, MyService) {
return {
restrict: 'E',
controller: ['$scope', '$rootScope', function($scope, $rootScope) {
$rootScope.$on('TEST', function() {alert("Event Caught")})
}]};
}];
UPDATE: It looks like my directive has not initiated by the time the event is broadcast. Is there a way to make it such that I could "wait for directive to instantiate" as opposed to waiting an arbitary "1000" ms or another alternative?
I think the only case your directive needs to catch that event is to get some initial data depending on the MyController because if your directive is independent of the MyController, you don't need to catch that event, just initiate the directive independently.
My solution is using $watch to get notified when the initial data from the MyController is ready.
app.controller('MyController', function($scope) {
$scope.data = {}; //initialize the data, this data could be fetched from an ajax
});
directiveModule.directive('MyDirective', ['MyService',
function(MyService) {
return {
restrict: 'E',
controller: ['$scope', function($scope) {
$scope.$watch("data",function(newValue,OldValue,scope){
//do your work here when data is changed.
});
}]};
}];
In case you use isolate scope in your directive:
directiveModule.directive('MyDirective', ['MyService',
function(MyService) {
return {
restrict: 'E',
scope : {
directiveData:"=",
},
controller: ['$scope', function($scope) {
$scope.$watch("directiveData",function(newValue,OldValue,scope){//watch on the directive scope property
//do your work here when data is changed.
});
}]};
}];
Your html may look like this:
<my-directive directive-data="data" /> //bind isolate scope directive property with controller's scope property.
I think below questions are some similar cases like this:
AngularJS : Directive not able to access isolate scope objects
AngularJS : directives and scope
all modules injected into main module share same rootScope. $emit however travels up the scope tree, you need $broadcast.
If you $broadcast from $rootScope it will be received by all active scopes so in directive could actually use $scope.$on and wouldn't need to inject $rootScope just for the purpose of using $on
Similarly if directive is descendant of the controller, could also broadcast from controller scope
You can. Demo: http://plnkr.co/edit/vrZgnu?p=preview
A side note: if you try to communicate to children element that is lower in DOM tree, you should use $broadcast. $emit is used to communicate with parents elements ("up" direction).
But your true problem might be that when you tried to emit the event, the directive might not be initiated yet. I used $timeout service to wait 1000ms to broadcast the event.
Update
If you don't want to wait a arbitrary 1000ms, you can let your directive $emit "ready" event to inform controller that it's ready. Then broadcast from the controller.
Also removed the 1000ms in this demo http://plnkr.co/edit/GwrdIw?p=preview, everything still works. But I don't think this is a reliable solution.

Resources