*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.
Related
I'm new to angular and can't figure out how to call a directive function from the template. I have some fuctionality that will be reused throught the app and figured I would just make a directive with all the functionlity needed, that can easily be shared accross different modules. While searching for answers I came across this post: how-to-call-a-method-defined-in-an-angularjs-directive
which seems like a good solution. However, I can't seem to figure out why my directive method showPolicy() is not being called.
// controller:
(function(){
'use strict';
angular.module('releaseAppsModule')
.controller('releaseAppsController', releaseAppsController);
releaseAppsController.$inject = ['$rootScope',
'storageFactory',
'releaseAppsFactory',
'$modal',
'$translate',
'getIconFactory',
'$scope',
'$filter'];
function releaseAppsController($rootScope, storageFactory, releaseAppsFactory, $modal, $translate, getIconFactory, $scope, $filter) {
var vm = this;
vm.policyControl = {};
...
// controller template:
<tr ng-repeat="policyRelease in regionRelease.policyReleases | orderBy:vm.orderByField:vm.reverseSort" ng-if="policyRelease.status == 'NEW' || policyRelease.status == 'SCHEDULED'">
<td>
<policy control="vm.policyControl" release-item="policyRelease" class="release-apps-app-btn app-release-data"></policy>
</td>
// directive:
(function(){
'use strict';
angular.module('myApp')
.directive('policy', policy)
function policy() {
var directive = {
restrict: 'E',
link: link,
replace: true,
scope: {
releaseItem: '=',
control: '='
},
template: '<a ng-click="vm.policyControl.showPolicy({releaseItem: releaseItem});">{{ releaseItem.policy.name }}</a>'
};
return directive;
function link(scope, el, attr) {
scope.internalControl = scope.control || {};
scope.internalControl.showPolicy = function (releaseData) {
...
} // showPolicy
scope.internalControl.showPolicyModal = function(response, releaseData) {
...
} // showPolicyModal
} // link
} // policy
})();
In your template, you're trying to call vm.policyControl.showPolicy() which is undefined on your current directive scope, as Angular is attempting to find
[directiveScope].vm.policyControl.showPolicy()
You'll need to change the ng-click function to internalControl.showPolicy(), as that is referencing the actual object that the directive's scope has available.
Hi I am trying to learn AngularJS Directives and I came really close but would like to extend my learning by cleaning and de-coupling my directive code.
Directive:
app.directive('ngSparkline', function () {
var url = "http://api.openweathermap.org/data/2.5/forecast/daily?mode=json&units=imperial&cnt=14&callback=JSON_CALLBACK&q=";
return {
restrict: 'A',
require: '^ngCity',
transclude: true,
scope: {
ngCity: '#'
},
templateUrl: 'app/partials/weatherTemplate.html',
controller: ['$scope', '$http', function($scope, $http) {
$scope.getTemp = function(city) {}
}],
link: function (scope, iElement, iAttrs) {
scope.getTemp(iAttrs.ngCity);
scope.$watch('weather', function (newVal) {
if (newVal) {
var highs = [];
angular.forEach(scope.weather, function (value) {
highs.push(value.temp.max);
});
chartGraph(iElement, highs, iAttrs);
}
});
}
};
});
As you can see I am not trying to write inline template rather use templateUrl. Now the problem is for the controller when I try using a .js controller instead of writing the controller code inline, I receive an error. How do I achieve this.
I tried:
I tried passing
controller: '#',
name: 'ctrl'
and I pass the 'ctrl' as:
<div ng-sparkline ng-city="San Francisco" ctrl="weatherController"></div>
it gives me controller not found. My project structure is something like below.
What am I doing wrong?
Is there a better/correct way of doing this?
Please suggest.
Note: I am learning this exercise from "http://www.ng-newsletter.com/posts/directives.html"
Why not just specify ng-controller on your element? If WeatherController is defined somewhere else then it doesn’t affect your directive definition, just leave the controller out of there.
<div ng-sparkline ng-city="San Francisco" ng-controller="WeatherController"></div>
Provided somewhere you do have the controller defined like
app.controller('WeatherController', ['$scope', '$http', function ($scope, $http) {
$scope.getTemp = // …
}]);
(BTW, if you noticed, the AngularJS convention is to name controllers in UpperCase fashion.)
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();
})
I've got an angular-fullstack project that I've been working on. Everything has been working fine, until I tried to make a directive. At first I thought my directive was too complex, but I've since tried with a stock, generated directive and I'm still getting the same error.
Directive is here:
'use strict';
angular.module('angularApp')
.directive('test', function () {
return {
templateUrl: 'app/test/test.html',
restrict: 'EA',
link: function (scope, element, attrs) {
}
};
});
I inject it here in main.js
'use strict';
angular.module('angularApp')
.controller('MainCtrl', function ($scope, $http, socket, characterService, test) {
$scope.path = new Array();
$scope.heroes = new Array();
characterService.all().success(function(data) {
$scope.heroes = data;
});
});
Everything works fine as long as I don't have test in the list of injected stuff, but if it's there, I get the error.
angular.js:11607Error: [$injector:unpr] Unknown provider: testProvider <- test <- MainCtrl
Is there somewhere else I need to add it? I've tried adding it to the dependency list like this:
angular.module('angularApp')
.controller('MainCtrl', ['test', function ($scope, $http, socket, characterService, test)
...
But that doesn't seem to make any difference.
What would be the point of injecting a directive in a ... controller?
It strictly makes no sense, since directive aims to build a new HTML attribute/markup:
<test></test>
for instance.
Besides, a directive can define/use a controller in its implementation.
Don't confuse the whole ;)
I'm trying to pass in the url for the template via a scope variable. The scope will not change so the template doesn't need to update based on it, but currently the scope variable is always undefined.
<div cell-item template="{{col.CellTemplate}}"></div>
Ideally the directive would be:
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
scope: {
template: '#template'
},
templateUrl: template // or {{template}} - either way
};
}])
This doesn't work however. I've tried a lot of different permutations in accomplishing the same concept, and this seems the closest, however it still doesn't work.
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
scope: {
template: '#template'
},
link: function (scope, element, attrs) {
var templateUrl = $parse(attrs.template)(scope);
$http.get(templateUrl, { cache: $templateCache }).success(function (tplContent) {
element.replaceWith($compile(tplContent)(scope));
});
}
};
}])
I've also tried using ng-include, but that also doesn't evaluate scope variables before compiling. The CellTemplate value is coming from a database call so is completely unknown before evaluation. Any suggestions for getting this working would be greatly appreciated!
Edit:
I'm using angular 1.0.8 and am not able to upgrade to a newer version.
You are not far off at all.
You don't need to use an isolated scope for the directive. You can pass the templateUrl like this:
<div cell-item template="col.CellTemplate"></div>
Then add a watch to detect when the template value changes:
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
restrict: 'A',
link: function(scope , element, attrs) {
scope.$watch(attrs.template, function (value) {
if (value) {
loadTemplate(value);
}
});
function loadTemplate(template) {
$http.get(template, { cache: $templateCache })
.success(function(templateContent) {
element.replaceWith($compile(templateContent)(scope));
});
}
}
}
}]);
Here is a working Plunker: http://plnkr.co/edit/n20Sxq?p=preview
If you don't want to deal with the linking logic yourself, or you want the isolate scope, I think this is simpler:
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
scope: {
template: '#template'
},
template: "<div ng-include='template'></div>"
};
}])
or:
template:"<ng-include src='template'></ng-include>"
It's an old post but I thought its useful if anyone lands in here for the answer.
You can try the templateUrl function as #caub mentioned in a comment. Same can also be used for components.
.directive("cellItem", ["$compile", '$http', '$templateCache', '$parse', function ($compile, $http, $templateCache, $parse) {
return {
templateUrl: function(element, attrs) {
return attrs.template || 'someDefaultFallback.html';
}
};
}]);
We don't need any of the injected dependencies here. Hope this helps someone.