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.
Related
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>
I've lost my bearings with regards to why I can't pass updates to my directives.
What I'm trying to accomplish with the following piece of code is to be able to set focus on by pressing a button. The problem is however that the "focus" binding on drInput only ever is set when the directive have loaded when it should change whenever it changes in drWrap. How come and how do I get around this?
And now, ladies and gentlemen, I present to you: THE CODE!
<div ng-app="myApp">
<dr-wrap></dr-wrap>
</div>
var myApp = angular.module('myApp', []);
myApp.directive('drWrap', function($timeout) {
return {
scope: {
focus: '=?bind'
},
restrict: 'E',
replace: 'true',
template: '<div><button ng-click="openSearch()">focus</button><dr-input focus="focusSearch" /></div>',
link: function(scope, elem, attr){
scope.openSearch = function(){
$timeout(function(){
scope.focusSearch = true
alert('scope.focusSearch 2 = ' + scope.focusSearch)
}, 1000)
}
}
};
})
.directive('drInput', function() {
return {
scope: {
focus: '=?bind'
},
restrict: 'E',
replace: 'true',
template: '<input type="test" focus-me="{{ focus }}" />',
link: function(scope, elem, attr){
scope.$watch('focus', function(value){
if(value != undefined){
scope.focus = value
alert('scope.focus = ' + scope.focus)
}
})
}
};
})
.directive('focusMe', ['$timeout', function ($timeout) {
return {
link: function (scope, element, attrs) {
attrs.$observe('focusMe', function(value){
if ((value === true) || (value == 'true')) {
$timeout(function () {
element[0].focus()
scroll(element[0])
})
} else {
element[0].blur()
}
})
}
}
}])
And the FIDDLE!
https://jsfiddle.net/L56rdqLp/168/
When you write scope: { focus: '=?bind' }, this means that the attribute name should be bind but not focus, so the template of drWrap should look like:
template: '<div><button ng-click="openSearch()">focus</button><dr-input bind="focusSearch" /></div>'
Add ngBlur event handler to drInput directives input like:
template: '<input type="test" focus-me="{{ focus }}" ng-blur="focus = false"/>',
to change the model to false, when input has lost its focus.
Here is working fiddle.
I would like to have a global handler for all the ng-dblclick occured from button, and input type="submit". How do i achieve this behaviour, by specifying and inject it in app.init.js file?
Thank you
Use angular directive to catch click or dbclick event
app.directive("ngdblclick", ['$compile', function($compile){
'use strict'
return{
compile : function(elements, attributes){
return{
restrict: 'C',
post : function(scope, element, attribute){
var isSingleClick = true;
element.bind('click', function(){
setTimeout(function(){
if(isSingleClick){
console.log("It's a single click");
return;
}
}, 500);
});
element.bind('dblclick', function(){
console.log("It's a double click");
isSingleClick = false;
setTimeout(function(){
isSingleClick = true;
return;
}, 500);
});
}
};
}
};
}]);
What you want is a custom directive that will extract all its child elements(in this case buttons and input type submit) and adds the ng-dblclick directive to them:
Look at the below example:
Example#1 :
angular.module('myApp', [])
.controller('MyController', MyController)
.directive('globalDblClick', globalDblClickDirective);
function MyController($scope) {
$scope.times = 0;
$scope.globalDblClick = function() {
$scope.times++;
};
}
function globalDblClickDirective($compile) {
return {
restrict: 'A',
scope: {
globalDblClick: '&'
},
link: function(scope, elem, attr) {
elem.find('button').each(function(index, btnElem) {
btnElem = angular.element(btnElem);
btnElem.attr('ng-dblclick', attr.globalDblClick);
$compile(btnElem)(scope);
});
elem.find('input[type="submit"]').each(function(index, inpElem) {
inpElem = angular.element(inpElem);
inpElem.attr('ng-dblclick', attr.globalDblClick);
$compile(inpElem)(scope);
});
}
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<section global-dbl-click="globalDblClick()">
<button>Click Me Twice</button>
<input type="submit" value="Double Click Me" />
<input type="text" value="Won't work here" />
<div> DoubledClicked: {{times}} times </div>
</section>
</div>
If you want to manually add; You can write a custom directive that you can use in place of ng-dblclick and internally it will add ng-dblclick directive if the type is button or input of type submit.
Note: Below example is just for the demonstration of the above concept in reality, you wouldn't use this as you can directly use ng-dblclick.
Check the below example:
angular.module('myApp', [])
.controller('MyController', MyController)
.directive('globalDblClick', globalDblClickDirective);
function MyController($scope) {
$scope.times = 0;
$scope.globalDblClick = function() {
$scope.times++;
};
}
function globalDblClickDirective($compile) {
return {
priority: 1001, // compiles first
terminal: true, // prevent lower priority directives to compile after it
restrict: 'A',
scope: {
globalDblClick: '&'
},
compile: function(tElement, tAttrs) {
if (tElement.is('button') || tElement.is('input[type="submit"]')) {
tElement.removeAttr('global-dbl-click'); // to avoid infinite compile loop
tElement.attr('ng-dblclick', tAttrs.globalDblClick);
var linkFn = $compile(tElement);
return function(scope) {
linkFn(scope);
};
}
}
};
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<button class="dblClick" global-dbl-click="globalDblClick()">Click Me Twice</button>
<input type="submit" class="dblClick" global-dbl-click="globalDblClick()" value="Double Click Me" />
<span> DoubledClicked: {{times}} times </span>
</div>
I have a directive which shows input fields and I want to initialize those fields with data from the server. The problem is that I can't do that while using ng-model.
Before using a directive I used in the controller something like $scope.field1 = $scope.first.field1
Here's my code. I simplified it for the sake of readability but the idea's here.
In my controller I have this code:
app.controller('MyController',
['$scope', 'myData', function($scope, myData) {
myData.then(function(data) {
$scope.first = data.first;
$scope.second = data.second;
});
}]);
Inside first and second I have 2 field: field1 and field2.
In in html code, I have this bit:
<h1>First</h1>
<my-directive info="first"></my-directive>
<h1>Second</h1>
<my-directive info="second"></my-directive>
The directive is as follows:
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
info: '='
},
templateUrl: 'static/js/myDirective.html',
link: function(scope, element, attrs) {
scope.doStuff = function() {
/* Do stuff with
scope.field1 and scope.field2 */
}
}
};
});
and the myDirective.html code:
<input type="text" ng-model="myfield1" />
<input type="text" ng-model="myfield2" />
<input type="submit" ng-click="doStuff()" />
If in myDirective.html I write:
<input type="text" value="info.field1" />
I can see the value fine.
Any ideas?
probably your directive initializes before your data loads. and your directive sees ng-model as an undefined variable. You are not using info directly in template so no auto $watch's for you :).
you need to $watch your info variable in directive and call your doStuff function on change.
note: i wouldn't recommend adding a controller to a directive just for this task. adding a controller to a directive is needed when you need to communicate with other directives. not for waiting async data
edit
you should do
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
info: '='
},
templateUrl: 'static/js/myDirective.html',
link: function(scope, element, attrs) {
scope.doStuff = function() {
/* Do stuff with
scope.field1 and scope.field2 */
}
scope.$watch('info', function(newValue){
scope.doStuff();
});
}
};
});
Check working demo: JSFiddle.
Define a controller for the directive and do the initialization inside it:
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
info: '='
},
controller: ['$scope', 'myData', function ($scope, myData) {
myData.then(function(data){
$scope.first = data.first;
$scope.second = data.second;
});
}],
...
},
Inside the directive, myfield1 and myfield2 don't exist. You are close to solving the issue by using info.field1 instead.
var myApp = angular.module('myApp', []);
//Faking myData here; in your example this would come from the server
var myData = {
first: {
field1: "FirstField1",
field2: "FirstField2"
},
second: {
field1: "SecondField1",
field2: "SecondField2"
}
};
myApp.controller('MyController', ['$scope', function($scope) {
$scope.first = myData.first;
$scope.second = myData.second;
}]);
myApp.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
info: '='
},
template: '<input type="text" ng-model="info.field1" /><input type="text" ng-model="info.field2" /><input type="submit" ng-click="doStuff()" />',
link: function(scope, element, attrs) {
scope.doStuff = function() {
alert('Info: ' + scope.info.field1 + ', ' + scope.info.field2);
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<h1>First</h1>
<my-directive info="first"></my-directive>
<h1>Second</h1>
<my-directive info="second"></my-directive>
</div>
What I'm doing wrong?
After of the click on "Click me" text in status block should appear a new string. (actually 4 new lines, but there are only 2)
(JSFiddle)
<div ng-app="myApp">
<div ng-controller="myController">
<button my-directive status="status" clickHandler="handler(message)">Click me</button>
<pre>{{ status }}</pre>
</div>
</div>
Script:
var app = angular.module('myApp', []);
app.controller('myController', ['$scope', '$timeout',
function myController ($scope, $timeout) {
$scope.status = 'Start controller.\n'; // update status
$scope.someData = 'Hello from controller !!!\n';
$scope.handler = function handler (message) {
$scope.status += $scope.someData; // must update status, but doesn't do it
$scope.status += 'Message from directive: ' + message; // must too
}
}]);
app.directive('myDirective', function myDirective () {
return {
scope: { clickHandler: '&', status: '=' },
link: function postLink (scope, element, attr) {
scope.status += 'Start link in myDirective.\n'; // update status
element.on('click', function onClick (event) {
scope.status += 'Click is fired.\n'; // update status
scope.$apply(function scopeApply () {
scope.status += 'In apply context.\n'; // update status
scope.clickHandler('"I\'m from directive."\n');
});
});
}
}
});
HTML
The attribute name should be dash-separated:
<button my-directive status="status" click-handler="handler(message)">
Click me
</button>
JS
The method should be called by passing in an object:
scope.clickHandler({message:'"I\'m from directive."\n'});
Updated Fiddle