I have a web app with directives in which the template url is build through a factory, but sometimes I also need the value of one of the attributes, so I do something like this:
myDirective.$inject = ['myFactory'];
function myDirective(myFactory) {
return {
restrict: 'E',
replace: true,
scope: {},
bindToController: {
...
},
controller: myDirectiveController,
templateUrl: function(elem, attrs) {
return angular.isDefined(attrs.myval) ? myFactory.url() : myFactory.altUrl();
}
}
}
Now, I'm changing a few of these to components, and I didn´t manage to do the same, as now components are objects instead of functions.
I can do without using attrs, like this
angular.component('myDirective', {
bindings: {
...
},
controller: 'myDirectiveController as vm',
templateUrl: function(myFactory) {
return myFactory.url();
}
}
but if I try to use also the attrs
angular.component('myDirective', {
bindings: {
...
},
controller: 'myDirectiveController as vm',
templateUrl: function(elem, attrs, myFactory) {
return angular.isDefined(attrs.myval) ? myFactory.url() : myFactory.altUrl();
}
}
I get an angular error. Any idea on how to do this?
This is because with a component, templateUrl became an injectable function:
If templateUrl is a function, then it is injected with the following locals:
$element - Current element
$attrs- Current attributes object for the element
This means you can inject $element and $attrs like any other service, factory, value, etc. you would inject into the templateUrl. This is instead of just receiving the element and attrs as always the first 2 parameters (that can be named anything), like the templateUrl in a directive:
Directive:
templateUrl: function(tElement, tAttrs)
Component:
templateUrl: function(someService, $attrs, $element, someOtherService)
By this logic, in the example you showed above, the error is coming from the fact that angular can't find a service or local with the name elem or attrs.
Related
I have a simple tab directive and I need a way to create custom routes by this directive attributes. I know I can create routes by app.config( function ($stateProvider) {...} ), but it doesn't work inside link directive function. Is there any way to do it?
UPDATE: made simple example what I mean
https://plnkr.co/edit/ZNDcZc217H2PRVHYWLA7?p=preview
myApp.directive('navTab', ["$compile", function ($compile) {
return {
restrict: 'E',
link: function (scope, element, attrs) {
myApp.config( function ($stateProvider) {
$stateProvider.state({
name: attrs.sref,
url: attrs.sref,
template: 'Hello!'
});
});
element.replaceWith('<a ui-sref="'+attrs.sref+'">Hello '+attrs.sref+'</a>');
$compile(element.contents())(scope);
}
}
}]);
I have a directive defined this way:
angular
.module('ui-abc', [ 'xyz'])
.directive('computer', compute);
function compute( item) {
return {
restrict: "EA",
templateUrl: '../elements/src/template.html',
scope:{ qwerty : '=',}
controller: ['$scope','$sce', {
.........
}
The controller does not have a name, how can i add one ? I need this name so that I can refer to that controller inside my ui-router configuration. I tried to do this
function compute( item) {
return {
restrict: "EA",
templateUrl: '../elements/src/template.html',
controller: "controllerName"
}
app.controller("controllerName", function(){......
....
App is not defined in this module, it is defined in the main app.js controller file.
So, it does not work, what do I have wrong?
here, you app will be - var app = angular.module('ui-abc', [ 'xyz']);
And don't use controller: "something", if you don't have one. It'll be fine if you just skip that line.
In your directive you can put a parameter called controllerAs
directive.js
function compute( item) { return {
restrict: "EA",
templateUrl: '../elements/src/template.html',
controller: 'controllerName',
controllerAs: 'vm'
};}
you can now call the controller as vm in your template.html
template.html
<body><h1>{{ vm.variableName }}</h1></body>
You can add the param called 'controllerAs' as below:
function compute( item) {
return {
restrict: "EA",
templateUrl: '../elements/src/template.html',
controller: "controllerName",
controllerAs: 'cName'
};
}
I am building a huge form that calls various directives to build a complete form. The Main Page calling the Form Builder passes the ng-model data like this:
<div form-builder form-data=“formData”></div>
Then the Form Builder Page calls various child directive to build various sections of the Form:
FormBuilder.html:
<div form-fields></div>
<div photo-fields></div>
<div video-fields></div>
.. etc.. etc...
When using $scope in controller, I had no problem accessing the $scope in the child directives like this:
function formBuilder() {
return {
restrict: 'A',
replace: true,
scope: {
formData: '='
},
templateUrl: 'FormBuilder.html',
controller: function($scope) {
$scope.formSubmit = function() {
// Submits the formData.formFields and formData.photoFields
// to the server
// The data for these objects are created through
// the child directives below
}
}
}
}
function formFields() {
return {
restrict: 'A',
replace: true,
templateUrl: 'FormFields.html',
controller: function($scope) {
console.log($scope.formData.formFields);
}
}
}
function photoFields() {
return {
restrict: 'A',
replace: true,
templateUrl: 'PhotoFields.html',
controller: function($scope) {
console.log($scope.formData.photoFields);
}
}
}
... etc..
But ever since I got rid of the $scope and started using ControllerAs, I am having all sorts of trouble accessing 2 way binding with the Parent - Child Controllers.
function formBuilder() {
return {
restrict: 'A',
replace: true,
scope: {
formData: '='
},
templateUrl: 'FormBuilder.html',
controller: function() {
var vm = this;
console.log(vm.formData); // Its fine here
vm.formSubmit = function() {
// I cannot change formData.formFields and formData.photoFields
// from Child Directive "Controllers"
}
},
controllerAs: ‘fb’,
bindToController: true
}
}
function formFields() {
return {
restrict: 'A',
replace: true,
templateUrl: 'FormFields.html',
controller: function() {
var vm = this;
console.log(vm.formData.formFields);
// No way to access 2 way binding with this Object!!!
}
}
}
function photoFields() {
return {
restrict: 'A',
replace: true,
templateUrl: 'PhotoFields.html',
controller: function() {
var vm = this;
console.log(vm.formData.photoFields);
// No way to access 2 way binding with this Object!!!
}
}
}
Whatever I try, I am reaching a road block. Things I have tried are:
Isolated Scopes: I tried passing formData.formFields and
formData.photoFields as isolated scopes to the child directive,
but I then end up getting the $compile: MultiDir error due to
nested isolated scopes so it is not possible.
If I don’t have
individual directives for each form section and have all of them in
1 directive under formBuilder directive, then it becomes a
humungous directive. The above is just a sketch but each child
directive builds 1 big form put together in the end. So merging them
together is really the last resort since it does become hard to
maintain and unreadable.
I don’t think there is a way to access
Parent directive’s ControllerAs from Child Directive's Controller any other way
from what I have seen so far.
If I use the parent’s ControllerAs in
the child directive template’s ng-model like <input type=“text” ng-model=“fb.formData.formFields.text" />, that works fine, but I
need to access the same from the Child directive’s controller for
some processing which I am unable to do.
If I get rid of the
controllerAs and use the $scope again, it works like before but I am
trying to get rid of the $scope altogether to prepare myself for
future Angular changes.
Since it is an advanced form, I need to have separate directive to handle various form sections and since nested isolated scopes are not allowed since Angular 1.2, it is making it ever harder especially when trying to get rid of $scope using ControllerAs.
Can someone guide me what are my options here please? I thank you for reading my long post.
Basically you need to use require option of directive (require option is used for communicate directive with directive). Which will give access to its parent controller by just mentioning require option in child directive. Also you need to use bindToController: true which will basically add isolated scope data to the directive controller.
Code
function formBuilder() {
return {
restrict: 'A',
replace: true,
bindToController: true,
scope: {
formData: '='
},
templateUrl: 'FormBuilder.html',
controller: function($scope) {
$scope.formSubmit = function() {
// Submits the formData.formFields and formData.photoFields
// to the server
// The data for these objects are created through
// the child directives below
}
}
}
}
Then you need to add require option to child directives. Basically the require option will have formBuilder directive with ^(indicates formBuilder will be there in parent element) like require: '^formBuilder',.
By writing a require options you can get the controller of that directive in link function 4th parameter.
Code
function formFields() {
return {
restrict: 'A',
replace: true,
require: '^formBuilder',
templateUrl: 'FormFields.html',
//4th parameter is formBuilder controller
link: function(scope, element, attrs, formBuilderCtrl){
scope.formBuilderCtrl = formBuilderCtrl;
},
controller: function($scope, $timeout) {
var vm = this;
//getting the `formData` from `formBuilderCtrl` object
//added timeout here to run code after link function, means after next digest
$timeout(function(){
console.log($scope.formBuilderCtrl.formData.formFields);
})
}
}
}
function photoFields() {
return {
restrict: 'A',
replace: true,
require: '^formBuilder',
templateUrl: 'PhotoFields.html',
//4th parameter is formBuilder controller
link: function(scope, element, attrs, formBuilderCtrl){
scope.formBuilderCtrl = formBuilderCtrl;
},
controller: function($scope, $timeout) {
var vm = this;
console.log(vm.formData.photoFields);
//to run the code in next digest cycle, after link function gets called.
$timeout(function(){
console.log($scope.formBuilderCtrl.formData.formFields);
})
}
}
}
Edit
One problem with above solution is, in order to get access to the controller of parent directive in directive controller it self, I've did some tricky. 1st include the the formBuilderCtrl to the scope variable from link function 4th parameter. Then only you can get access to that controller using $scope(which you don't want there). Regarding same issue logged in Github with open status, you could check that out here.
I have a directive with dynamic controller which is passed via controller-name property.
Directive:
angular
.module('directives.panel', [])
.directive('panel', panel);
panel.$inject = ['$timeout', '$parse'];
function panel($timeout, $parse) {
var directive = {
restrict: 'EA',
transclude: true,
replace: true,
template: '<div class="panel panel-solid panel-table" ng-transclude></div>',
controller: '#',
name: 'controllerName',
controllerAs: 'panel',
link: linkFunc
};
return directive;
}
}
Is it possible to inject resolve 'taskbook' object into dynamic controller?
When I try to do that I get unknown provider. However injecting Resource service (GroupResource) works fine.
Is it possible to inject resolve in directive controller?
State
angular
.module('taskbooks.taskbook', [
'deployment.group',
'resource.deployment',
'resource.taskbook'
])
.config(TaskbookConfig)
.controller('TaskbookController', TaskbookController);
TaskbookConfig.$inject = ['$stateProvider', '$provide'];
function TaskbookConfig($stateProvider) {
$stateProvider
.state('taskbooks.taskbook', {
url: 'taskbooks/:taskbookId',
parent: 'taskbooks',
views: {
"mainContent#taskbooks": {
controller: 'TaskbookController as taskbook',
templateUrl: 'taskbook/taskbook.tpl.html'
}
},
resolve: {
taskbook: TaskbookPrepare
}
});
}
TaskbookPrepare.$inject = ['$stateParams', 'TaskbookResource'];
function TaskbookPrepare($stateParams, TaskbookResource) {
return TaskbookResource.get({
taskbookId: $stateParams.taskbookId
}).$promise;
}
Directive Controller
angular
.module('deployment.groups', ['resource.group'])
.controller('DeploymentGroupController',DeploymentGroupController);
DeploymentGroupController.$inject = ['$scope', '$element', '$attrs', 'GroupResource', 'taskbook'];
function DeploymentGroupController($scope, $element, $attrs, GroupResource, taskbook) {
}
Sort of... If this directive requires the resolve that is associated with this state, then it's safe to assume it will only ever be used in this state, correct?
Going on that idea, the directive can reference the controller set in the state and you can add the resolve to the controller scope.
Here's a very simplified version of what I'm saying...
angular
.module('taskbooks.taskbook')
.config([ $stateProvider, function ($stateProvider) {
$stateProvider
.state('taskbook', {
url: '/:id',
parent: 'taskbooks',
resolve: {
taskbook: [ '$stateParams', 'TaskbookResource', function ($stateParams, TaskbookResource) {
return TaskbookResource.get({
taskbookId: $stateParams.taskbookId
}).$promise;
}]
},
controller: ['taskbook', function (taskbook) {
this.taskbook = taskbook;
}],
controllerAs: 'taskController'
});
}])
.directive('someDirective', function() {
return {
restrict: 'EAC',
controller: 'taskController',
link: function (scope, el, attr, ctrl) {
var taskbook = ctrl.taskbook;
}
}
});
Do note, I removed a lot of the non-relevant code from yours just to get the point across quicker. Obviously this can be reworked into the structure you've written.
I made an AngularJS directive which works in AngularJS 1.2 but after updating my application to AngularJS 1.3 the fourth parameter of my link function does not contain an array of controller instances anymore but an array of $get.Constructor {} items.
jtApp.directive("jtWizard", ["$q", "$timeout", function ($q, $timeout) {
return {
require: ["^ngController", "jtWizard"],
restrict: "E",
replace: false,
transclude: true,
templateUrl: "App/Components/wizard.htm",
scope: {
mainHeader: "#"
},
controller: "jtWizardController",
link: function postLink(scope, elm, attrs, controllers, transcludeFn) {
...
var userWizardController = controllers[0];
var jtWizardController = controllers[1];
...
}
);
Any ideas about that?
1.3 changes how directive controllers are created, controllers are now instances of Constructor, I believe it is a side effect of commit 5f3f25a1. This also means you cannot return an object to define a controller, rather must modify the controllers members directly.
controller: function() { this.foo = "bar"; }
rather than
controller: function() { return { foo: "bar" }; }
Functionality should otherwise remain backwards compatible.