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

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

Related

AngularJs 1: How to inject a service to a controller from the directive declaration

I've created a directive for managing (creating/updating/deleting) comments on customers in my application.
Now I'd like the exact same comment functionality (look and feel) on users, so the only thing I need to do is replace my CustomersService with my UsersService.
Both services have the same methods for "crud-ing" comments (I would make both implement an ICommentsService interface if possible in javascript).
My question is how can I in the best possible way reuse my already created View and Controller so I don't have to duplicate code?
My initial approach is to create two separate directives (CustomerComments and UserComments) that reference the same view and controller, but injecting a CustomersService or a UsersService respectively. The problem I'm facing with this, is how to keep the "DI-definition" in the directive declaration whilst having the controller in a separate file...?
This is my directive declaration:
angular.module('myApp').directive('myComments', [
function() {
'use strict';
return {
restrict: 'E',
scope: {
commentId: '='
},
templateUrl:'mycomments.html',
controller: 'CommentsCtrl', //<-- How to define injected objects here?
};
}
]);
...and this is my controller:
angular.module('myApp').controller('CommentsCtrl', [
'$scope',
'$q',
'CustomersService', //<-- These "DI-objects" should be defined in the directive declaration
function ($scope, $q, commentsService) {
$scope.addComment = function(comment){
commentsService.addComment(comment);
};
$scope.getComment = function(commentId){
retur commentsService.getComment(commentId);
};
//...etc...
}
]);
Or are there better ways to solve this?
Usually you do not explicitly register a directive's controller. That is somewhere you would have:
function CommentsCtrl($scope, $q, commentsService) {
And in your directive(s):
controller: ['$scope','$q','CustomersService', CommentsCtrl]

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.

ui-router controller stateparams

I have the following usecase:
I have two views which are identical, as well as two controllers which are very similar. I intend to extend one controller with the other and just override the diff. The problem I am having is in ui-router I want to choose a controller by name (Since the templates is shared, and the controllers differ this cannot be declared in the template itself)
Before this refactoring I had the following code:
.state('edit-page-menu', {
url: '/sites/:siteId/page_menus/:menuId',
templateUrl: 'partials/edit-menu.html',
controller: ['$scope', '$stateParams', (scope, stateParams) => {
scope.siteId = stateParams.siteId
scope.menuId = stateParams.menuId
}]
})
Which adds the stateParams to the scope. In this usecase I had the controller defined in the template and could modify the scope that way. However now when I switch to controller by name approach
.state('edit-page-menu', {
url: '/sites/:siteId/menus/:menuId',
templateUrl: 'partials/edit-menu.html',
controller: 'editMenuCtrl'
})
I don't know how to inject / add the $stateParams to the controller. And I would really like to avoid injecting "$state" in the controller and fetch the parameters that way. Is there a way to modify the scope of a controller if you choose it by name?
Best regards.

How to get $stateParams in parent view?

I want to make tabs with tab-content.
tab-content has it's own view.
Here is code sample
(function () {
angular
.module('infirma.konfiguracja', ['ui.router'])
.config(routeConfig)
;
routeConfig.$inject = ['$stateProvider'];
function routeConfig($stateProvider) {
$stateProvider
.state('app.konfiguracja', {
url: 'konfiguracja/',
views: {
'page#app': {
templateUrl: 'app/konfiguracja/lista.html',
controller: 'konfiguracjaListaCtrl',
controllerAs: 'vm'
}
},
ncyBreadcrumb: {label: "Ustawienia systemu"}
})
.state('app.konfiguracja.dzial', {
url: '{dzial:.*}/',
views: {
'dzial#app.konfiguracja': {
templateUrl: 'app/konfiguracja/dzial.html',
controller: 'konfiguracjaDzialCtrl',
controllerAs: 'vm'
}
},
ncyBreadcrumb: {label: "{{vm.nazwaDzialu}}"}
})
;
}
})();
I want to mark selected tab which is in parent state (app.konfiguracja).
Problem is that when entering url like /konfiguracja/firmy/ there is no $stateParams.dzial in app.konfiguracja controller
How to fix it?
I created working example for your scenario here. I would say, that there at least two ways.
The first, general way, how we should use the UI-Router and its selected params in parent views (to mark selected tab/link), should be with a directive **ui-sref-active**:
ui-sref-active="cssClassToBeUsedForSelected"
So this could be the usage:
<a ui-sref="app.konfiguracja.dzial({dzial: item.id})"
ui-sref-active="selected"
>{{item.name}}</a>
The second approach (my preferred) would be to use a reference Model, created in parent $scope, and filled in a child:
.controller('konfiguracjaListaCtrl', ['$scope', function ($scope, )
{
$scope.Model = {};
}])
.controller('konfiguracjaDzialCtrl', ['$scope', function ($scope)
{
$scope.Model.dzial = $scope.$stateParams.dzial;
// we should be nice guys and clean after selves
$scope.$on("$destroy", function(){ $scope.Model.dzial = null });
}])
usage could be then like this
<span ng-if="item.id == Model.dzial">This is selected</span>
How is the second approach working? check the DOC:
Scope Inheritance by View Hierarchy Only
Keep in mind that scope properties only inherit down the state chain if the views of your states are nested. Inheritance of scope properties has nothing to do with the nesting of your states and everything to do with the nesting of your views (templates).
It is entirely possible that you have nested states whose templates populate ui-views at various non-nested locations within your site. In this scenario you cannot expect to access the scope variables of parent state views within the views of children states.
Check that all in action here

How to call parent method from model controller

I have one controller named "contractController"
contractController contains a method save()
contractController opens one model window with controller named "PopUpcontroller"
from PopUpcontroller i want to call save method on contractController
tried to call save method like $parent but nothing works.
Please advice.
If you are using Angular UI Bootstrap (http://angular-ui.github.io/bootstrap/), then, make use of the "resolve" attribute when you call $modal.open().
Whatever you add to resolve, will be available if you hook it up as a dependency injection.
In the example on their page, the "item" is available as below because it is hooked up in "resolve".
Resolve:
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: 'ModalInstanceCtrl',
size: size,
resolve: {
items: function () {
return $scope.items;
}
}
});
Use:
angular.module('ui.bootstrap.demo').controller('ModalInstanceCtrl', function ($scope, $modalInstance, items) {
If the two controllers are nested so that:
<div ng-controller="contractController">
<div ng-controller="PopUpcontroller">
</div>
</Div>
you can just call $scope.save() on the popUpController and it automatically goes up to find a parent with that method (till the $rootScope).
if they are not nested you should use the services in order to perform the communication among controllers
You need to make sure the popup is created from contractController's scope if you want to retain its prototype. How are you creating the popup?
Something like this in Angular UI Boostrap (I don't use it but the API should accept it):
$modal.open({
scope: $scope,
/* ... other opts */
});

Resources