AngularJS: minifications breaks my directive - angularjs

I use a directive to ask user for action confirmations in modals.
It works like a charm during development, but, after minification, it's broken.
This is the dreadful "$injector: unpr" error I get:
Error: [$injector:unpr] Unknown provider: aProvider <- a
...
I presume the problem is that $scope and $modalInstance are renamed, and should not be, but I don't know how to avoid this...
This is the directive code:
'use strict';
app.directive('reallyClick', ['$modal', function($modal) {
var modalInstanceCtrl = function ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
return {
restrict: 'A',
scope: {
reallyClick: '&',
item: '='
},
link: function (scope, element, attrs) {
element.bind( 'click', function() {
var message = attrs.reallyMessage || 'Are you sure?';
var modalHtml = '<div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>';
var modalInstance = $modal.open({
template: modalHtml,
controller: modalInstanceCtrl
});
modalInstance.result.then(function () {
scope.reallyClick({item:scope.item}); // raise an error : $digest already in progress
}, function() {
// modal dismissed
});
});
}
};
}]);
I use it this way:
...
<td title="Delete customer">
<button
class="btn btn-primary glyphicon glyphicon-trash"
really-message="Are you really sure to remove customer <i>{{customer.name}}</i> ?" really-click="deleteCustomer(customerId)"
></button>
</td>
...
If it can be of any help, these are the modules I use during the build phase:
'auto_install',
'clean:dist',
'favicons',
'wiredep',
'useminPrepare',
'concurrent:dist',
'autoprefixer',
'concat',
'ngmin',
'copy:dist',
'cdnify',
'cssmin',
'uglify',
'filerev',
'usemin',
'htmlmin',
and these are the modules I inject in my app:
var app = angular.module('smallBusinessApp', [
'ngSanitize',
'ngRoute',
'firebase',
'ui.bootstrap',
]);

The modalInstance Controller needs to be created with the dependency injection syntax as well,
'use strict';
app.directive('reallyClick', ['$modal', function($modal) {
return {
restrict: 'A',
scope: {
reallyClick: '&',
item: '='
},
link: function (scope, element, attrs) {
element.bind( 'click', function() {
var message = attrs.reallyMessage || 'Are you sure?';
var modalHtml = '<div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>';
var modalInstance = $modal.open({
template: modalHtml,
controller: modalInstanceCtrl
});
modalInstance.result.then(function () {
scope.reallyClick({item:scope.item}); // raise an error : $digest already in progress
}, function() {
// modal dismissed
});
});
}
};
}]);
ModelInstanceController:
app.controller('modalInstanceCtrl',['$scope','$modalInstance',function ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]);
Was a problem for me too and had to separate the controller part of the modal and do it like this, hope it helps!!

I guess it's your modalInstanceCtrl. Try to not implement is as an object inside your directive, but as a controller:
app.controller('modalInstanceCtrl', [ '$scope', '$modalInstance',
function ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close();
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
}]);
And lateron reference it by putting it in quotes:
var modalInstance = $modal.open({
template: modalHtml,
controller: 'modalInstanceCtrl'
});

Related

Bind a callback to directive through template tag in Angular

I am trying to bind a callback to a component through a template. The template contains instance of another directive. This just doesn't seems to work. I'm invoking the directive from a modal, not sure if this can cause a problem. I tried many of the solution suggested in previous questions and still no luck. I ran it with a debugger, and the '$ctrl.onSelectionChanged' is defined to be as it should:
function (locals) { return parentGet(scope, locals); }
My code:
my-component.js:
The inner-directive as no reference to the callback, should it have?
angular.module('myModule')
.component('myComponent', {
template: '<div class="container-fluid"> <inner-directive><button class="btn btn-default center-block" ng-click="$ctrl.onSelectionChange({items_list: $ctrl.selectedItems})">Button</button> </inner-directive> </div>',
bindings: {
$router: '<',
onSelectionChange: '&'
},
controller: MyComponentController
});
/* #ngInject */
function MyComponentController(MyService, $filter, $log, $q) {
var $ctrl = this;
$ctrl.$routerOnActivate = function () {
};
$ctrl.selectedItems = [];
}
calling-component-controller.js:
function CallingComponentCtrl(toastr, $scope, $uibModal, $log) {
var $ctrl = this;
$ctrl.loadDone = false;
$ctrl.grid = {
enableSorting: true,
data: [],
columnDefs: [
{name: 'id'},
{name: 'name'},
{name: 'description'}
],
enableRowSelection: true,
enableRowHeaderSelection: false,
multiSelect: false,
noUnselect: true,
onRegisterApi: function (gridApi) {
$ctrl.gridApi = gridApi;
}
};
this.$onInit = function () {
if (angular.isUndefined($ctrl.abc)) {
return;
}
syncData();
$ctrl.loadDone = true;
};
this.$onChanges = function () {
// TODO
};
function syncData(){
$ctrl.grid.data = $ctrl.abc;
}
$ctrl.myFoo = function(items_list) {
alert("This is never happening");
};
$ctrl.onPress = function (event) {
var modalInstance = $uibModal.open({
template: '<my-component on-selection-change="$ctrl.myFoo(items_list)"></my-component>',
windowClass: 'modal-window'
});
};
}
Any thoughts?
Use the $compile service
link: function(scope, element) {
var template = $compile('<div class="container-fluid"> <inner-directive><button class="btn btn-default center-block" ng-click="$ctrl.onSelectionChange({items_list: $ctrl.selectedItems})">Button</button> </inner-directive> </div>')(scope);
element.append(template);
}
Remember to inject the compile service to the directive function
Trying changing your child component to this:
.component('myComponent', {
template: '<div class="container-fluid"> <inner-directive><button class="btn btn-default center-block" ng-click="$ctrl.onSelectionChange({items_list: $ctrl.selectedItems})">Button</button> </inner-directive> </div>',
bindings: {
$router: '<'
},
require: {
parent: '^^CallingComponent'
},
controller: MyComponentController
});
With require you inherit the parent controller.
Then in the init function you can make the call:
function MyComponentController(MyService, $filter, $log, $q) {
this.$onInit = function() {
this.parent.myFoo(items_list);
}
var $ctrl = this;
$ctrl.$routerOnActivate = function () {};
$ctrl.selectedItems = [];
}
--Old answer
Try changing the template to:
<my-component on-selection-change="$ctrl.myFoo(items_list)"></my-component>
You're calling it from the $scope when it's declared as a controller function.
Well, I found the problem. While invoking the modal, I used a template used a component in this template. To the component, I passed a callback that is defined in the '$ctrl'. The problem was that the modal defined its own scope and couldn't reached this $ctrl. So I defined a controller to the modal, and called through it the function I needed. This is my solution, I highlighted the changes and adds:
calling-component-controller.js:
function CallingComponentCtrl(toastr, $scope, $uibModal, $log) {
var $ctrl = this;
....
$ctrl.myFoo = function(items_list) {
alert("This is never happening");
};
$ctrl.onPress = function (event) {
var modalInstance = $uibModal.open({
template: '<my-component on-selection-change="$ctrl.myNewFoo(items_list)"></my-component>',
**controllerAs: '$ctrl',**
windowClass: 'modal-window',
**controller: function($uibModalInstance){
var $ctrl = this;
$ctrl.myNewFoo= function(items_list) {
$uibModalInstance.close(items_list);
};
}**
});
**modalInstance.result.then(function(items_list) {
$ctrl.myFoo(items_list);
});**
};
}

AngularJs ui.bootstrap - Modal show error [$injector:unpr]

i am trying to use angular ui.bootstrap to implement some functionality like this but this shows an error
[$injector:unpr]
http://errors.angularjs.org/1.5.2/$injector/unpr?p0=%24modalProvider%20%3C-%20%24modal%20%3C-%20ngReallyClickDirective
my code is
app.js
var app = angular.module('app',['ui.router','oc.lazyLoad','ui.bootstrap','ngReallyClickModule']);
ngReallyClickModule.js
angular.module('ngReallyClickModule',['ui.router'])
.directive('ngReallyClick', ['$modal',
function($modal) {
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
return {
restrict: 'A',
scope:{
ngReallyClick:"&",
item:"="
},
link: function(scope, element, attrs) {
element.bind('click', function() {
var message = attrs.ngReallyMessage || "Are you sure ?";
var modalHtml = '<div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>';
var modalInstance = $modal.open({
template: modalHtml,
controller: ModalInstanceCtrl
});
modalInstance.result.then(function() {
scope.ngReallyClick({item:scope.item});
}, function() {
//Modal dismissed
});
});
}
}
}
]);
View
<a ng-really-message="Are you sure ?" ng-really-click="test(item)" item="item" ng-repeat="item in [1,2,3,4,5]">Delete</a>
i want modal dialog on ng-really-click click. And call a function of the current controller on click of the modal Ok button.
i am using angular-ui-router and oclazyLoading
The answer is from the error, your are using some provider that you have not defined/injected.
It is hard to say exactly what is the problem considering you have many injections here, but from the error you gave us, there is no provider for $modal. AngularUI recently switched to uib prefixing most of its providers and directives (i.e. $uibModal). Try using the new version.
https://angular-ui.github.io/bootstrap

Angular popup with dynamic template and controller

I have found the following code that produces a popup window.
angular.module('plunker', ['ui.bootstrap', 'myModal']);
angular.module("myModal", []).directive("myModal", function ($modal, $timeout) {
"use strict";
return {
template: '<div ng-click="clickMe(rowData)" ng-transclude></div>',
replace: true,
transclude: true,
scope: {
rowData: '&myModal'
},
link: function (scope, element, attrs) {
scope.clickMe = function () {
$modal.open({
template: "<div>Created By:" + scope.rowData().data + "</div>"
+ "<div class=\"modal-footer\">"
+ "<button class=\"btn btn-primary\" ng-click=\"ok()\">OK</button>"
+ "<button class=\"btn btn-warning\" ng-click=\"cancel()\">Cancel</button>"
+ "</div>",
controller: function ($scope, $modalInstance) {
$scope.ok = function () {
$modalInstance.close({ test: "test"});
};
$scope.cancel = function () {
};
$timeout(function () {
$(".modal-dialog").draggable();
var resizeOpts = {
handles: "all", autoHide: true
};
$(".modal-dialog").resizable(resizeOpts);
}, 0);
},
backdrop: "static"
});
}
}
};
});
I would like to make this code generic and assign templates and their controllers on the fly. I guess I could specify a template with controller built-in but want to avoid doing that.
Thanks

How to use controller array notation when a controller is "created on the fly"?

I know how to use Angular's array notation for controllers when these are created "within an app", like this:
angular.module('appName').controller('controllerName', ['$scope', '$http', function ($scope, $http) {
}]);
But what if I have a controller that is used "on the fly"?
I have a directive that creates a bootstrap-ui modal controlled by a controller created "in the moment", this is the code:
angular.module('appName').directive('ngConfirmClick', ['$modal', function($modal) {
// Controller "on the fly"
var modalController = function($scope, $modalInstance) {
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
return {
restrict: 'A',
scope: {
ngConfirmClick:"&"
},
link: function(scope, element, attrs) {
element.bind('click', function() {
var message = attrs.ngConfirmMessage || "Really?";
var modalHtml = '<div class="modal-header"><h3 class="modal-title">Confirm</h3></div>'
+ '<div class="modal-body">' + message + '</div>'
+ '<div class="modal-footer"><button class="btn btn-default" ng-click="ok()">OK</button>'
+ '<button class="btn btn-default" ng-click="cancel()">Cancel</button></div>';
var modalInstance = $modal.open({
template: modalHtml,
controller: modalController, // Controller reference
backdrop: false,
});
modalInstance.result.then(function() {
scope.ngConfirmClick();
}, function() {
});
});
}
}
}]);
Question is... how do I use array notation in here?
// Controller "on the fly"
var modalController = function($scope, $modalInstance) {
...
};
I know I could register the controller as I usually do with reusable controllers (see my first block of code)... but: should I? is it the only way?
This question can be abstracted and be helpful to others if stated as this:
If a directive needs to create/use a controller and this controller is used only by this directive and nobody else... how should the controller be created? within the app? within what?
You can add an $inject property...
// Controller "on the fly"
var modalController = function($scope, $modalInstance) {
...
};
modalController.$inject = ['$scope', '$modalInstance'];
What you're doing is already fine, you just need to assign modalController using the array notation. You don't need to add it as a reusable controller in the app since it's only used inside the directive.
JAVASCRIPT
var modalController = ['$scope', '$modalInstance', function($scope, $modalInstance) {
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
}];
Alternatively, you can also add the dependencies using the $inject property.
var modalController = function($scope, $modalInstance) {
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
modalController.$inject = ['$scope', '$modalInstance'];

Angular directive check element?

I'm hooking up a $modal service for confirmation boxes in my app and made a directive that only works for ng-click. Well I also need it to work for ng-change so I did it like the following:
.directive('ngConfirmClick', ['$modal',
function($modal) {
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
return {
restrict: 'A',
scope:{
ngConfirmClick:"&",
item:"="
},
link: function(scope, element, attrs) {
element.bind('click', function() {
var message = attrs.ngConfirmMessage || "Are you sure ?";
if(element == 'select'){
var modalHtml = '<div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-success" ng-model="" ng-change="ok()">OK</button><button class="btn btn-warning" ng-change="cancel()">Cancel</button></div>';
} else {
var modalHtml = '<div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-success" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>';
}
var modalInstance = $modal.open({
template: modalHtml,
controller: ModalInstanceCtrl
});
modalInstance.result.then(function() {
scope.ngConfirmClick({item:scope.item});
}, function() {
});
});
}
}
}
]);
You can see I'm trying to check if the element is a 'select' element but I'm not sure how angular's link method/function reads the element. Can I check it with a string like how I did it? (It doesn't work when I try this btw).
How can I check if the element I'm attaching my directive to is a select?
Angular's jqLite is a subset of jQuery and that is the element parameter passed into the link function (unless you load the full jQuery library, then it will be a jQuery object). As described in this post using element.prop('tagName') will return the element type which is a method included in the jqLite library.
So I got confused and the if statement should of been at the element.bind not at the var modalHtml...
Here's the updated code for me to get this to work with both ng-change and ng-click. I just added bind on click and bind on change with an if statement to check the element.context.tagName was select or not
directive('ngConfirmClick', ['$modal',
function($modal) {
var ModalInstanceCtrl = function($scope, $modalInstance) {
$scope.ok = function() {
$modalInstance.close();
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
};
return {
restrict: 'A',
scope:{
ngConfirmClick:"&",
item:"="
},
link: function(scope, element, attrs) {
console.log(element.context.tagName);
if(element.context.tagName == 'SELECT'){
element.bind('change', function() {
var message = attrs.ngConfirmMessage || "Are you sure ?";
var modalHtml = '<div class="modal-header"><h4 id="title-color" class="modal-title"><i class="fa fa-exclamation"></i> Please Confirm</h4></div><div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>';
var modalInstance = $modal.open({
template: modalHtml,
controller: ModalInstanceCtrl
});
modalInstance.result.then(function() {
scope.ngConfirmClick({item:scope.item});
}, function() {
});
});
} else {
element.bind('click', function() {
var message = attrs.ngConfirmMessage || "Are you sure ?";
var modalHtml = '<div class="modal-header"><h4 id="title-color" class="modal-title"><i class="fa fa-exclamation"></i> Please Confirm</h4></div><div class="modal-body">' + message + '</div>';
modalHtml += '<div class="modal-footer"><button class="btn btn-primary" ng-click="ok()">OK</button><button class="btn btn-warning" ng-click="cancel()">Cancel</button></div>';
var modalInstance = $modal.open({
template: modalHtml,
controller: ModalInstanceCtrl
});
modalInstance.result.then(function() {
scope.ngConfirmClick({item:scope.item});
}, function() {
});
});
}
}
}
}
]);

Resources