I'm working on a hybrid Ionic1 app and I am not able to call a controller method from inside a directive.
The problem is that inside the directive I'm able to pass controller's variables (such as variable apps) but I am not able to call removeCredentials() inside card-app directive.
I've tried so many ways to make it work but none of them solved the problem. I've also tried using the & to bind the controller method inside the directive but clicking on the X icon in the directive template triggers nothing.
Right now the code looks like this:
Controller AppsCtrl in apps.js
angular.module('testprofile')
.controller('AppsCtrl', function($scope, $state, $log, $ionicHistory, $filter, $ionicPopup, UserData, CredentialStoreService, ionicMaterialMotion, ionicMaterialInk, Auth, Effects, CredentialStoreService, Globals, Validations) {
$scope.onlyMyApps = true;
$scope.showNoItems = false;
var translate = $filter('translate');
$scope.apps = [];
$scope.mapAppsFromCredentials = function(credentials) {
var map = credentials.map(function(cred) {
/* app: "App 1"
appId: 7
profile: "Default Profile"
profileId: 0
*/
var app = UserData.getLocalApp(cred.app);
if (!app) {
$log.debug("apps.js::mapAppsFromCredentials - no local app matching credentials: "+JSON.stringify(cred));
return null;
}
var curHelper = UserData.getCurrentAppHelper(app);
return {
used: Boolean(cred.used),
id: app.id,
title: app.nameApp,
profile: cred.profile,
desc: curHelper ? curHelper.name : 'NO HELPER'
};
})
return map;
}
$scope.refresh = function() {
$log.debug("apps.js::AppsCtrl - REQUEST CREDENTIAL LIST...");
CredentialStoreService.getCredentialList(
//ON SUCCESS
function(response) {
$log.debug("apps.js::AppsCtrl - ...CREDENTIAL LIST RESPONSE: " + JSON.stringify(response));
$scope.apps = $scope.mapAppsFromCredentials(response);
Effects.init();
$scope.showNoItems = true;
for (var i = 0; i < $scope.apps.length; i++) {
if ($scope.apps[i].used) {
$scope.showNoItems = false;
break;
}
}
},
//ON ERROR
function() {
$log.debug("apps.js::AppsCtrl - ...CREDENTIAL LIST ERROR");
}
)
}
$scope.$on("$ionicView.enter", function () {
$log.debug("apps.js::$ionicView.enter - ENTER");
$scope.refresh();
});
//REMOVE CREDENTIALS
$scope.removeCredentials = function() {
$log.debug("app-details.js::removeCredential CONFIRMATION...");
$ionicPopup.confirm({
title: translate('Remove Credentials'),
template: translate('Are you sure you want to delete current credentials') + ' ' + app.nameApp + '?',
cancelText: translate('No'), // String (default: 'Cancel'). The text of the Cancel button.
//cancelType: '', // String (default: 'button-default'). The type of the Cancel button.
okText: translate('Yes'), // String (default: 'OK'). The text of the OK button.
okType: 'button-energized-900' // String (default: 'button-positive'). The type of the OK button.
}).then(function(res) {
if(res) {
$log.debug("app-details.js::removeCredential CONFIRMED!");
Auth.askPIN(true)
.then( function() {
CredentialStoreService
.removeCredential(
//authenticationProfileName
UserData.getAuthProfile(app).name,
//appProfileName
app.name,
// ON SUCCESS
onRemovedCredential,
// ON ERROR
Globals.onError);
});
} else {
$log.debug("app-details.js::removeCredential REJECTED.");
}
});
};
var onRemovedCredential = function() {
$log.debug("app-details.js::removeCredential CREDENTIALS REMOVED!");
Effects.showToast(translate('CREDENTIALS REMOVED')+'.');
$ionicHistory.nextViewOptions({
disableBack: true
});
$state.go('menu.apps', {}, {reload:true});
};
})
Directives in inclusions.js
angular.module('testprofile')
.directive('cardProfile', function() {
return {
scope: {
data:"=cardSource"
},
restrict: 'E',
templateUrl: 'app/templates/card-profile.html'
};
})
.directive('cardApp', function() {
return {
scope: {
data:"=cardSource",
},
restrict: 'E',
templateUrl: 'app/templates/card-app.html',
controller: 'AppsCtrl',
controllerAs: "ac",
bindToController: true,
link: function(scope, element, attrs, ctrl) {
scope.removeCredentials = function() {
return scope.ac.removeCredentials();
};
}
};
})
.directive('pinRequired', function(Auth) {
return {
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
attrs.authorized = false;
element.on('click', function(e){
// console.log("\t\t + attrs.authorized:"+attrs.authorized);
if (attrs.authorized) {
if (attrs.href) location.href = attrs.href;
return true;
} else {
e.preventDefault();
e.stopPropagation();
Auth.askPIN(true)
.then(function() {
// console.log("inclusions.js::pinRequired - askPin - AUTHORIZED");
//THE PIN IS CORRECT!
attrs.authorized = true;
element.triggerHandler('click');
attrs.authorized = false;
});
}
});
}
};
})
;
apps.html
<ion-view view-title="Apps">
<ion-content padding="true" class="has-header bg-stable">
<h4 class="list-title"><i class="material-icons"></i> {{ 'App List' | translate }}
<!--label class="toggle toggle-calm pull-right toggle-small">
<span class="toggle-label" translate>My Apps</span>
<input type="checkbox" ng-model="onlyMyApps">
<div class="track">
<div class="handle"></div>
</div>
</label-->
</h4>
<ion-list class="">
<card-app ng-repeat="app in apps | filter:(onlyMyApps || '') && {used: true}"
card-source="app"
class="{{app.used ? 'used' : 'unused'}}"></card-app>
<div class="item padding" ng-show="showNoItems"><p class="text-center">{{ 'No saved credentials' | translate}}</span>.</p>
</div>
</ion-list>
</ion-content>
</ion-view>
card-app.html
<ion-item
menu-close=""
class="card item-thumbnail-left item-icon-right app-item">
<!--<img ng-src="{{data.thumb}}">-->
<span style="line-height:70px;vertical-align:middle" class="item-image avatar-initials h2">{{data.title|initials}}</span>
<div class="item">
<h2 class="inline-block">
<span ng-bind="data.title"></span>
</h2>
<span class="icon-right material-icons" ng-click='removeCredentials()'></span>
<div>
<span>{{'Active helper' | translate}}</span>
<h3>{{ data.desc | translate }}</h3>
</div>
</div>
</ion-item>
Solution: at the end the click event was not triggered because I had to add pointer-events: auto; and cursor: pointer; in my css
If your directive needs to call just a single function from a parent controller, just pass it as parameter (like you tried).
First ensure that controller scope is available for directive. For example create simple method and call it before directive:
// to ensure scope is ok
<button ng-click="alertMethod()"></button>
<card-app ng-repeat='' card-source='' class='' my-method="alertMethod()"></card-app>
// directive
//
scope: {
myMethod: '&'
},
template: "<button ng-click='myMethod()'>Click</button>"
That should work, but if your controller scope is not available maybe you need to use named controllers:
https://docs.angularjs.org/api/ng/directive/ngController
Using controller as makes it obvious which controller you are accessing in the template when multiple controllers apply to an element.
Than in your code:
// controller
this.alertMethod = function() {
alert('Hello');
}
// view
<div ng-app="myApp" ng-controller="testCtrl as ctrl">
<card-app ng-repeat='' card-source='' class='' my-method="ctrl.alertMethod()"></card-app>
Related
I am using directive which is using ng-file-upload for uploading files. This directive is included in modal window called from main controller. I am calling $rootScope.$broadcast event from main controller and listening for that event in directive. I have issues when user is opening modal twice( or more times), it seems that directive scope for each invoked directive is not destroyed and it goes 2 times in scope.$on directive call.
Modal View
<div class="dup-group-doc2 col-md-9" data-ng-if="carvm.isEdit">
<upload-document add-button="true" delete-button="false" review-obj="carvm.review" document-types="carvm.documentTypes" agencies="carvm.agenciesByReview" module="'CAR'" show-upload-button="false" show-add-button="true" event="carvm.event">>
</upload-document>
</div>
Main controller
function openImpModal(isMultiple, isEdit, finding, eventId, event) {
followupvm.isMultiple = isMultiple || false;
followupvm.isEdit = isEdit || false;
followupvm.finding = finding;
followupvm.eventId = eventId;
followupvm.impModalInstance = $uibModal.open({
animation: true,
size: 'lg',
templateUrl: "src/app/modules/planning/views/modals/implementation.modal.view.html",
controller: 'ImplementationModalController as impvm',
backdrop: 'static',
keyboard: false
});
followupvm.impModalInstance.result.then(function(selectedItem) {
if (selectedItem.object !== {}) {
$rootScope.$broadcast('callDirective', object);
}
}
});
Directive View
<div class="form-group row">
<label for="customDocName" class="col-md-4 control-label text-right">Document Name:</label>
<input type="text" class="col-md-3" id="customDocName" placeholder="Name document (optional)" data-ng-model="reviewObj.docObj.docName" data-ng-change="onChange()">
<input ng-if="!isEdit" type="file" class="col-md-5" ngf-select ng-model="$parent.docFile" name="file" ngf-max-size="5MB" required value="Choose File" ngf-model-invalid="errorFile" validfile>
</div>
Directive controller
(function() {
'use strict';
angular
.module('planning')
.directive('uploadDocument', uploadDocument);
uploadDocument.$inject = ['DocumentService', 'Upload', 'configParams', '$timeout', '$rootScope', 'CommentService'];
/* #ngInject */
function uploadDocument(DocumentService, Upload, configParams, $timeout, $rootScope, CommentService) {
var directive = {
link: link,
restrict: 'AE'
scope: {
index: "#?",
addButton: "=?",
loggedUser: "=",
deleteButton: "=?",
reviewObj: "=?",
documentTypes: "=",
agencies: "=?",
module: "=",
event: "=?",
showUploadButton: "=?",
showAddButton: "=?",
isEdit: "=?",
fileObj: "=?"
},
templateUrl: 'src/app/modules/planning/directives/document-upload/documentUpload.view.html'
};
return directive;
function link(scope, element, attrs) {
scope.reviewObj.docObj = {};
scope.docFile = {};
scope.reviewObj.docObj.restricted = false;
scope.showPreviousComments = false;
scope.documentTypeRequired = false;
scope.objectAdded = false;
scope.uploadClicked = false;
/* istanbul ignore if */
if (scope.isEdit) {
scope.reviewObj.docObj.docName = scope.fileObj.documents.documentName;
if (scope.reviewObj.docObj.docName === 'undefined') {
scope.reviewObj.docObj.docName = '';
}
scope.reviewObj.docObj.restricted = scope.fileObj.documents.restricted;
scope.reviewObj.docObj.selectedDoc = scope.fileObj.documents.refDocTypeId;
scope.reviewObj.docObj.previousComments = scope.fileObj.comments;
}
scope.uploadDoc = function(file, newEvent) {
// code for uploading document
};
$rootScope.$on('callDirective', function(event, object) {
**// If I open same modal twice second time when $rootScope event is send it will go twice to this $on event**
if (scope.module === object.moduleName) {
if (!scope.objectAdded) {
scope.eventModule = object.moduleName;
scope.event = object.event;
scope.findingsIdList = object.findingsIdList;
scope.reviewObj.docObj = object.uploadObject;
scope.objectAdded = true;
scope.uploadDoc(object.fileToUpload, true);
}
}
});
}
}
})();
I can't say if your directive is destroyed or not, but with your current code, your listener is not destroyed with the scope anyway.
$rootScope.$on('callDirective', function(event, object) { ... });
This line returns a unregistering function. You have to use it when your scope is destroyed.
var unregister = $rootScope.$on('callDirective', function(event, object) { ... });
scope.$on('$destroy', unregister);
I am working on a directive to create a custom dropdown. I have managed to bring the directive code to get it replaced.
Below is the directive tag in the html
<dropdown placeholder="Country.." list="count" selected="item" property="name"></dropdown>
Below is the html template that replaces the directive tag
<div class="dropdown-container" ng-class="{ show: listVisible }">
<div class="dropdown-list">
<div>
<div ng-repeat="item in list" ng-click="select(item)" ng-class="{ selected: isSelected(item) }">
<span>{{property !== undefined ? item[property] : item}}</span>
<i class="fa fa-check"></i>
</div>
</div>
</div>
Below is the angularjs directive
App.directive("dropdown", function ($rootScope) {
return {
restrict: "E",
templateUrl: "/app/dropdownTemplate.html",
scope: {
placeholder: "#",
list: "=",
selected: "=",
property: "#"
},
link: function (scope) {
scope.listVisible = false;
scope.isPlaceholder = true;
scope.select = function (item) {
scope.isPlaceholder = false;
scope.selected = item;
};
scope.isSelected = function (item) {
return item[scope.property] === scope.selected[scope.property];
};
scope.show = function () {
scope.listVisible = true;
};
$rootScope.$on("documentClicked", function (inner, target) {
console.log($(target[0]).is(".dropdown-display.clicked") || $(target[0]).parents(".dropdown-display.clicked").length > 0);
if (!$(target[0]).is(".dropdown-display.clicked") && !$(target[0]).parents(".dropdown-display.clicked").length > 0)
scope.$apply(function () {
scope.listVisible = false;
});
});
scope.$watch("selected", function (value) {
scope.isPlaceholder = scope.selected[scope.property] === undefined;
scope.display = scope.selected[scope.property];
});
}
}
});
I am trying to get the list of countries from another controller whose function is as follows,
addressService.getCountries().success(function (response) {
angular.copy(response, $scope.list);
}
How do I bind the values from the controller to my directive when the page loads?
EDIT: What do I do as the directive loads before the function addressService.getCountries() gets called?
you can make a factory that will be useful throughout project to make to make a variable accessible whenever and wherever, in any controller or in any directive you want to use that variable you can easily access
this is how we can make factory,
(function() {
"use strict";
angular.module('dataModule',[])
.factory('datafactory',function(){
return {
credentials : {},
};
});
})();
After making this factory you can inject this module in your module and this factory in your controller
whenever you want you can use this like
addressService.getCountries().success(function (response) {
datafactory.country = response
}
in your controller
$scope.myvar =datafactory.country
in future you use this factory for any such variable that will be accessible everywhere for example you want to store list of state globally
datafactory.state =["abc","def","xyz"]
Consider the initial app main state:
$stateProvider.state('main', {
url: '',
views: {
'nav#': {
templateUrl: baseTemplatesUrl + 'nav.main.html',
controllerAs: 'vm',
controller: 'mainNavController',
resolve: {
timelinesService: 'timelinesService'
}
},
'content#': {
template: ''
}
}
});
Inside the nav.main.html template I'm trying to use a simple slider directive:
<ul>
<li ng-repeat="t in vm.timelines">
<!-- ui-sref=".timeline({yearFrom: t.yearFrom, yearTo: t.yearTo})" -->
<a class="button tiny" ui-sref-active="active">{{t.text}}</a>
</li>
</ul>
<div simple-slider items="vm.timelines" on-item-clicked="vm.timelineClicked"></div>
The directive is defined as:
function simpleSlider() {
return {
restrict: 'EA',
templateUrl: baseTemplatesUrl + 'directive.simpleSlider.html',
replace: true,
scope: {
items: '=',
onItemClicked: '&'
},
link: function(scope, el, attr, ctrl) {
scope.curIndex = 0;
scope.next = function() {
scope.curIndex < scope.items.length - 1 ? scope.curIndex++ : scope.curIndex = 0;
};
scope.prev = function() {
scope.curIndex > 0 ? scope.curIndex-- : scope.curIndex = scope.items.length - 1;
};
scope.$watch('curIndex', function() {
scope.items.forEach(function(item) {
item.active = false;
});
scope.items[scope.curIndex].active = true;
});
},
};
}
The problem
On a page load the directive's $watch throws an exception saying that scope.items[scope.curIndex] is undefined, whereas the ng-repeat="t in vm.timelines" inside the nav.main.html template renders successfully.
Why the watch fails / how to pass vm.timelines to directive's scope?
The fiddle
https://jsfiddle.net/challenger/Le5p9aup/
Update 1. Regarding #Lex answer
The fiddle is working. But my setup differs from the given fiddle. In my setup the timelinesService returns the $http promise:
var service = {
getTimelines: function() {
return $http.get('/api/timelines');
}
};
then inside the navMainController the vm.timelines array gets populated:
vm.timelines = [];
timelinesService.getTimelines().then(function(response) {
vm.timelines = response.data.timelines;
}).catch(function(error) {
console.log(error);
});
Then, unless you click the directive's prev/next button, the $watch will fail:
// scope.items[scope.curIndex] is undefined
scope.items[scope.curIndex].active = true;
I'd like to use angular component with ui.bootstrap.modal. angular version is 1.5.
I tried to use like below.
component
function MyComponentController($uibModalInstance){
var ctrl = this;
ctrl.doSomething = function() {
//doSomething
}
}
app.component('myComponent', {
contoller: MyComponentController,
templateUrl: '/path/to/myComponent.html'
}
parent controller
function parentController($uibModal){
var ctrl = this;
ctrl.openModal = function(){
var modalInstance = $uibModal.open({
template: '<my-component></my-component>'
}
}
And when I execute parentController.openModal() , I got the error of $injector:unpr Unknown Provider although modal window is open.
Is there a way to use angular component with ui.bootstrap.modal?
If you need more information, please let me know that.
Thank you.
EDIT
I've got a way to use component with ui.bootstrap.modal from Renato Machado, Thanks Renato.
But I feel It's a little bit complicated and not convenient. So finally I think that it's better to use component inside the modal.
Modal is opened with regular way(just set controller and template in $uibModal.open({}) ) and the modal contains the component which has logics you want to use commonly.
Modal should have only simple logics that are related with modal like close modal window.
Another logics mainly related with business/Application should be in component.
It makes easy to commonalize.
EDIT: As of UI Bootstrap 2.1.0 there is native support for component in bootstrap modals. It looks like there have been several quick releases after 2.1.0 to fix some issues with the modals, so I'd be sure to grab the latest.
See this Plunk for a version using UI Bootstrap 2.1.0+
http://plnkr.co/edit/jy8WHfJLnMMldMQRj1tf?p=preview
angular.module('app', ['ngAnimate', 'ui.bootstrap']);
angular.module('app')
.component('myContent', {
template: 'I am content! <button type="button" class="btn btn-default" ng-click="$ctrl.open()">Open Modal</button>',
controller: function($uibModal) {
$ctrl = this;
$ctrl.dataForModal = {
name: 'NameToEdit',
value: 'ValueToEdit'
}
$ctrl.open = function() {
$uibModal.open({
component: "myModal",
resolve: {
modalData: function() {
return $ctrl.dataForModal;
}
}
}).result.then(function(result) {
console.info("I was closed, so do what I need to do myContent's controller now. Result was->");
console.info(result);
}, function(reason) {
console.info("I was dimissed, so do what I need to do myContent's controller now. Reason was->" + reason);
});
};
}
});
angular.module('app')
.component('myModal', {
template: `<div class="modal-body"><div>{{$ctrl.greeting}}</div>
<label>Name To Edit</label> <input ng-model="$ctrl.modalData.name"><br>
<label>Value To Edit</label> <input ng-model="$ctrl.modalData.value"><br>
<button class="btn btn-warning" type="button" ng-click="$ctrl.handleClose()">Close Modal</button>
<button class="btn btn-warning" type="button" ng-click="$ctrl.handleDismiss()">Dimiss Modal</button>
</div>`,
bindings: {
modalInstance: "<",
resolve: "<"
},
controller: [function() {
var $ctrl = this;
$ctrl.$onInit = function() {
$ctrl.modalData = $ctrl.resolve.modalData;
}
$ctrl.handleClose = function() {
console.info("in handle close");
$ctrl.modalInstance.close($ctrl.modalData);
};
$ctrl.handleDismiss = function() {
console.info("in handle dismiss");
$ctrl.modalInstance.dismiss("cancel");
};
}]
});
Original answer is below:
I was trying to figure this out the other day too. I took the information I found in this post along with this link to try and come up with an alternate way to accomplish this. These are some reference links I found that helped me:
https://github.com/angular-ui/bootstrap/issues/5683
http://www.codelord.net/ (this one helped in understanding passing arguments to callbacks in components)
Also here is a Plunk: http://plnkr.co/edit/PjQdBUq0akXP2fn5sYZs?p=preview
I tried to demonstrate a common real world scenario of using a modal to edit some data.
angular.module('app', ['ngAnimate', 'ui.bootstrap']);
angular.module('app')
.component('myContent', {
template: 'I am content! <button type="button" class="btn btn-default" ng-click="$ctrl.open()">Open Modal</button>',
controller: function($uibModal) {
$ctrl = this;
$ctrl.dataForModal = {
name: 'NameToEdit',
value: 'ValueToEdit'
}
$ctrl.open = function() {
$uibModal.open({
template: '<my-modal greeting="$ctrl.greeting" modal-data="$ctrl.modalData" $close="$close(result)" $dismiss="$dismiss(reason)"></my-modal>',
controller: ['modalData', function(modalData) {
var $ctrl = this;
$ctrl.greeting = 'I am a modal!'
$ctrl.modalData = modalData;
}],
controllerAs: '$ctrl',
resolve: {
modalData: $ctrl.dataForModal
}
}).result.then(function(result) {
console.info("I was closed, so do what I need to do myContent's controller now and result was->");
console.info(result);
}, function(reason) {
console.info("I was dimissed, so do what I need to do myContent's controller now and reason was->" + reason);
});
};
}
});
angular.module('app')
.component('myModal', {
template: `<div class="modal-body"><div>{{$ctrl.greeting}}</div>
<label>Name To Edit</label> <input ng-model="$ctrl.modalData.name"><br>
<label>Value To Edit</label> <input ng-model="$ctrl.modalData.value"><br>
<button class="btn btn-warning" type="button" ng-click="$ctrl.handleClose()">Close Modal</button>
<button class="btn btn-warning" type="button" ng-click="$ctrl.handleDismiss()">Dimiss Modal</button>
</div>`,
bindings: {
$close: '&',
$dismiss: '&',
greeting: '<',
modalData: '<'
},
controller: [function() {
var $ctrl = this;
$ctrl.handleClose = function() {
console.info("in handle close");
$ctrl.$close({
result: $ctrl.modalData
});
};
$ctrl.handleDismiss = function() {
console.info("in handle dismiss");
$ctrl.$dismiss({
reason: 'cancel'
});
};
}],
});
There is no need to make it more complicated by passing along the parent controller, you can just access it from within the .component that displays the modal.
Component
/**
* #ngdoc component
* #name fsad.component:video
*
* #description <fsad-video> component, in development...
*
*/
(function () {
'use strict';
angular.module('fsad').component('fsadVideo', {
bindings: {},
templateUrl: function(appConstant){return appConstant.paths.modules.fsad + 'leefloon/fsad-video.html'},
controller: controller
});
controller.$inject = ['$scope'];
function controller($scope){
var $ctrl = this;
setDataModel();
/****************************************************************/
$ctrl.ui.close = close;
/****************************************************************/
function setDataModel(){
$ctrl.ui = {};
}
function close(){
$scope.$parent.$close();
}
}
}());
Opening the modal
var modalInstance = $uibModal.open({
backdrop: 'static',
keyboard: true,
backdropClick: false,
template: '<fsad-video></fsad-video>',
windowClass: 'edit-contactenblad',
});
Since you are stating that the template is a component, the $scope.$parent will always be pointing to the modal instance. Allowing you to access the $close() function.
Passing and receiving data
If you need to pass data to the component, or receive data back from the component, you can do it like this.
var modalInstance = $uibModal.open({
backdrop: 'static',
keyboard: true,
backdropClick: false,
template: '<fsad-video method="$ctrl.method" on-viewed="$ctrl.userHasViewedVideo(time)"></fsad-ideo>',
controller: function(){
this.method = method;
this.userHasViewedVideo = function(time){}
},
controllerAs: '$ctrl',
windowClass: 'edit-medewerker',
});
Just on a side note, i'm using this structure style guide to create the component.
If you want access to the $uibModal's $close() and $dismiss() functions, along with some parent data and function binding within your component, you can pass them all along as such:
Open Modal Logic
$uibModal.open({
template: '<login close="$close()" dismiss="$dismiss()" ' +
'email="$ctrl.cookieEmail" check-login="$ctrl.ajaxLogin(user, pass)"></login>',
controller: function () {
this.cookieEmail = $cookies.get('savedEmail');
this.ajaxLogin = AjaxLoginService.login;
},
controllerAs: '$ctrl'
});
Modal Login Component
{
templateUrl: 'view/login.html',
bindings: {
email: '<',
checkLogin: '&',
close: '&',
dismiss: '&'
},
controller: function () {
var viewModel = this;
viewModel.password = '';
viewModel.submit = function () {
viewModel.checkLogin(
{ user: viewModel.email, pass: viewModel.password }
).then(function (success) {
viewModel.close();
});
}
}
}
Modal HTML
<form ng-submit="$ctrl.submit()">
<input type="text" ng-model="$ctrl.email" />
<input type="password" ng-model="$ctrl.password" />
<button type="button" ng-click="$ctrl.dismiss()">Cancel</button>
<button type="submit">Login</button>
</form>
The AngularJS 1.5 docs are a little sparse, but they show the usage of the & binding as a function wrapper: https://docs.angularjs.org/guide/component
You need to pass the parent controller to the modal component with the modal instance on it. To do that you need to append the generate HTML of the modal in the parent component
parent component
$ctrl.openModal = function(){
$ctrl.modalInstance = $uibModal.open({
template: '<your-modal></your-modal>',
appendTo : $document.find('parentComponent')
});
}
modal component
.component('yourModal', {
templateUrl: 'path/to/modal.html',
replace: true,
require: {
parent : '^parentComponent'
},
controller: ModalCtrl
});
function ModalCtrl() {
var $ctrl = this;
$ctrl.$onInit = function(){
var instance = $ctrl.parent.modalInstance;
$ctrl.items = ['item1', 'item2', 'item3'];
$ctrl.selected = {
item: $ctrl.items[0]
};
$ctrl.ok = function () {
instance.close($ctrl.selected);
};
$ctrl.cancel = function () {
instance.dismiss('cancel');
};
instance.result.then(function (selectedItem) {
$ctrl.selected = selectedItem;
}, function () {
console.log('Modal dismissed at: ' + new Date());
});
};
}
Be carefull because the required controller will only be available after the $onInit.
Here is what my directive looks like
//noinspection JSCheckFunctionSignatures
angular.module('notificationDirective', []).directive('notification', function ($timeout) {
//noinspection JSUnusedLocalSymbols
return {
restrict: 'E',
replace: true,
scope: {
ngModel: '#'
},
template: '<div class="alert fade" bs-alert="ngModel"></div>',
link: function (scope, element, attrs) {
scope.$watch('message', function () {
console.log('message now: ' + JSON.stringify(scope.message));
// element.show();
// //noinspection JSUnresolvedFunction
// $timeout(function () {
// //element.empty();
// element.fadeOut("slow");
// }, 2000);
});
}
}
});
Here is my controller
function NotificationController($scope) {
$scope.message = {};
console.log('notification activated');
$scope.invite = function() {
console.log("submitting invite for " + $scope.email);
$scope.message.title = 'hello';
// console.log('message now is ' + JSON.stringify($scope.message, null, 2));
}
}
Here is how I use it
<form class="pure-form" data-ng-controller="NotificationController">
<input type="text" class="pure-input-1-2" placeholder="Email"
data-ng-model="email">
<button type="submit"
class="pure-button notice pure-button-xlarge"
data-ng-click="invite()">Invite me
</button>
</form>
<notification data-ng-model="message"></notification>
What I want
- Whenever value if scope.message changes in NotificationController, the directive has the new value so that I can alert that on web page
What I do not understand
It seems I am not understanding how $scope.$watch is working
Please help
You made several mistakes :
Your notification tag must be inside controller (in html) since it must have access to 'message' variable.
Your binding in your directive is wrong : you must use '=' instead of '#' (as Thalis said).
Variable 'message' does not exist in your directive, you must use scope.ngModel (which is binded to your message variable).
Callback given in your watcher will be executed each time your variable will be updated. Since you use an object, watcher will be executed when your variable reference change. You must set 'true' to your third parameter of your watcher to check for object equality).
Here is your sample :
HTML :
<body>
<div id="my-app" data-ng-app="myApp">
<form class="pure-form" data-ng-controller="NotificationController">
<input type="text" class="pure-input-1-2" placeholder="Email" data-ng-model="email" />
<button type="submit"
class="pure-button notice pure-button-xlarge"
data-ng-click="invite()">Invite me
</button>
<notification data-ng-model="message"></notification>
</form>
</div>
</body>
Javascript :
var myApp = angular.module('myApp', []);
myApp.directive('notification', function() {
return {
restrict: 'E',
replace: true,
scope: {
ngModel: '='
},
template: '<div class="alert fade" bs-alert="ngModel"></div>',
link: function (scope, element, attrs) {
scope.$watch('ngModel', function () {
console.log('message now: ' + JSON.stringify(scope.ngModel));
// element.show();
// //noinspection JSUnresolvedFunction
// $timeout(function () {
// //element.empty();
// element.fadeOut("slow");
// }, 2000);
}, true);
}
}
});
myApp.controller('NotificationController', ['$scope', function($scope) {
$scope.message = {};
console.log('notification activated');
$scope.invite = function() {
console.log("submitting invite for " + $scope.email);
$scope.message.title = 'hello';
// console.log('message now is ' + JSON.stringify($scope.message, null, 2));
};
}]);
See this fiddle : http://jsfiddle.net/77Uca/15/
I believe that in your directive definition you need:
ngModel: '='
instead of:
ngModel: '#'
If you want to use the '#', your view should be:
<notification data-ng-model="{{message}}"></notification>
Also in your $watch you should be watching for ngModel and not message.