Directive scope is not getting destroyed - javascript

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

Related

AngularJS can't call controller method inside directive

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>

Disable/re-enable button within isolate scope directive in Angular

I need to enable/disable a button after click with Angular. When a user clicks "Submit form", it makes an http request. If an error occurs (inside the catch), I want to re-enable the directive button so that a user can try again. I have a directive with controllerAs syntax and isolateScope. Below is the code I have (simplified for here);
myCtrl parent controller (controllerAs is myCtrl)
vm.submit = function() {
MyService
.update()
.then(function(res) {
// success
})
.catch(function(err) {
vm.error = true;
// error
});
};
Parent view with my-form directive
<my-form form-submit='myCtrl.submit()'></my-form>
myForm Directive
function myForm() {
return {
restrict: 'E',
replace: true,
templateUrl: 'myform.html',
scope: {
formSubmit: '&',
},
require: ['form', 'myForm'],
link: function(scope, element, attrs, ctrls) {
var formCtrl = ctrls[0];
var directiveCtrl = ctrls[1];
scope.isButtonDisabled = false;
scope.submit = function() {
scope.submitted = true;
directiveCtrl.submit();
};
},
controller: function($scope, $element, $attrs) {
var vm = this;
// Submit parent function
vm.submit = function() {
vm.formSubmit();
};
},
controllerAs: 'myFormCtrl',
bindToController: true
};
}
angular.module('mymodule')
.directive('myForm', [ myForm ]);
myForm directive template
<form name='myForm' novalidate>
// form fields
<button ng-click='submit()' ng-disabled='isButtonDisabled'>Submit Form</button>
</form>
Instead of having the promise resolved in the parent controller, you could just pass the promise back to the child controller and handle the logic there. Something like this should work:
parent:
vm.submit = function() {
return MyService.update().$promise;
};
child:
vm.submit = function() {
vm.formSubmit().then(function(res) {
// success
})
.catch(function(err) {
vm.error = true;
// error
scope.isButtonDisabled = false;
});
}

How to use angular component with ui.bootstrap.modal in angular 1.5?

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.

Angular ng show does not update while the scope does

I am setting up a <button> that is supposed to open a chat window.
the chat window has a ng-show depending on scope.openChat which is false to start.
When I clicked the button I update scope.openChat to true and use $scope.apply.
The scope seems to have updated but the ng-show does not do anything.
here is the html
<div ng-controller="MessagesCtrl">
<button start-chat>Send Messages</button>
</div>
and
<div ng-show="openChat" ng-controller="MessagesCtrl">
<div>CHAT WINDOW
</div>
</div>
here is the controller:
app.controller("MessagesCtrl", function MessagesCtrl($scope,Auth) {
$scope.openChat = false;
$scope.message = { recipient : undefined, sender: undefined, text:'text'};
});
Here is the directive for the button:
'use strict';
app.directive('startChat',[ 'Post', 'Auth', function (Post, Auth) {
return {
restrict: 'A',
replace: true,
bindToController: true,
controller: 'MessagesCtrl',
link: function(scope, element, attrs) {
element.bind('click', function() {
scope.$apply(function() {
scope.openChat = true;
scope.message.recipient = scope.profile.$id;
scope.message.sender = Auth.resolveUser().uid;
});
});
}
}
}]);
thank you
I'd suggest not creating two instances of MessagesCtrl. Here is a simplified working example of the issue you are trying to solve. Examine the markup and see that MessagesCtrl contains both of these elements. Otherwise, you were on the right track updating $scope and calling $apply
Also note that .on() is the preferred method to .bind() see jQuery docs
JSFiddle Link
<div ng-app="app">
<div ng-controller="MessagesCtrl">
<button start-chat>Send Messages</button>
<div class="chatWindow" ng-show="openChat"></div>
</div>
</div>
app.directive('startChat', [function () {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.on('click', function() {
scope.$apply(function() {
scope.openChat = true;
});
});
}
}
}]);
app.controller('MessagesCtrl', ['$scope', function($scope) {
$scope.openChat = false;
$scope.message = { recipient : undefined, sender: undefined, text:'text'};
}]);

angular.js directive two-way-binding scope updating

I wanted to use a directive to have some click-to-edit functionality in my front end.
This is the directive I am using for that: http://icelab.com.au/articles/levelling-up-with-angularjs-building-a-reusable-click-to-edit-directive/
'use strict';
angular.module('jayMapApp')
.directive('clickToEdit', function () {
return {
templateUrl: 'directives/clickToEdit/clickToEdit.html',
restrict: 'A',
replace: true,
scope: {
value: '=clickToEdit',
method: '&onSave'
},
controller: function($scope, $attrs) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
$scope.method();
};
}
};
});
I added a second attribute to the directive to call a method after when the user changed the value and then update the database etc. The method (´$onSave´ here) is called fine, but it seems the parent scope is not yet updated when I call the method at the end of the directive.
Is there a way to call the method but have the parent scope updated for sure?
Thanks in advance,
Michael
I believe you are supposed to create the functions to attach inside the linking function:
Take a look at this code:
http://plnkr.co/edit/ZTx0xrOoQF3i93buJ279?p=preview
app.directive('clickToEdit', function () {
return {
templateUrl: 'clickToEdit.html',
restrict: 'A',
replace: true,
scope: {
value: '=clickToEdit',
method: '&onSave'
},
link: function(scope, element, attrs){
scope.save = function(){
console.log('save in link fired');
}
},
controller: function($scope, $attrs) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
console.log('save in controller fired');
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
$scope.method();
};
}
};
});
I haven't declared the functions inside the controller before, but I don't see why it wouldn't work.
Though this question/answer explain it Link vs compile vs controller
From my understanding:
The controller is used to share data between directive instances, not to "link" functions which would be run as callbacks.
The method is being called but angular doesn't realise it needs to run the digest cycle to update the controller scope. Luckily you can still trigger the digest from inside your isolate scope just wrap the call to the method:
$scope.$apply($scope.method());

Categories

Resources