So I have this filter directive:
app.directive('filter', function(){
return {
restrict: 'E',
transclude: true,
scope: {
callFunc: '&'
},
template:
' <div>' +
' <div ng-transclude></div>' +
' </div>',
controller: function($scope, $element, $attrs){
this.getData = function() {
$scope.callFunc()
}
}
}
});
app.directive('positions', function(){
return {
require: '^filter',
scope: {
selectedPos: '='
},
template:
' Positions: {{selectedPos}}' +
' <ul>' +
' <li ng-repeat="pos in positions">' +
' {{pos.name}}</a>' +
' </li>' +
' </ul>',
controller: function($scope, $element, $attrs){
$scope.positions = [
{name: '1'},
{name: '2'},
{name: '3'},
{name: '4'},
{name: '5'}
];
$scope.selectedPos = $scope.positions[0].name;
$scope.setPosition = function(pos){
$scope.selectedPos = pos.name;
};
},
link: function(scope, element, attrs, filterCtrl) {
scope.posRegData = function() {
filterCtrl.getData();
}
}
}
})
And the controller:
app.controller('keyCtrl', ['$scope', function($scope) {
var key = this;
key.callFunc = function() {
key.value = key.selectedPos;
console.log(key.selectedPos)
}
}]);
The main question is why the key.selectedPos in the controller get's the right value only on the second click?
Here is a plunker replicating my issue.
One way of doing it is to send a param when calling callFunc()
Then, I update the func in the ctrl: key.callFunc = function(filterParams), but also, I am updating the passed method call-func="key.callFunc(filterParams)
Then in filter directive I change getData method to:
this.getData = function(val) {
$scope.callFunc({filterParams: val})
}
In positions directive I pass the value that I need:
scope.posRegData = function() {
filterCtrl.getData({position: scope.selectedPos});
}
Finally, in keyCtrl I get the value like this:
key.callFunc = function(filterParams) {
key.value = filterParams.position;
console.log(filterPrams.position)
}
Here is a plunker demonstrating this attempt.
The question in this case is if this is a good way of doing it, keeping in mind it's within a very large application.
That's because how isolated scopes work. The parent scope (the controller in your case) will be updated when the digest cycle runs, which is after your ng-click function called the callFunc. So you can put your callFunc code in a $timeout and it will work (but will cause another digest cycle).
Another solution will be to put the value in an object, so when you change the object the controller (which have the reference) will see the update immediately:
In the controller:
key.selectedPos = { value: {}};
key.callFunc = function() {
key.value = key.selectedPos.value;
console.log(key.selectedPos.value)
}
In the directive:
$scope.selectedPos.value = $scope.positions[0].name;
$scope.setPosition = function(pos){
$scope.selectedPos.value = pos.name;
};
See this plunker.
Related
I ran into a situation in AngularJS where I want to use a template that appends to the same template when data is changed instead of replacing the existing template. Directive can be something like this:
<my-template data="myData"></my-template>
template.html can be like:
<p>ID: {{data.id}}, Name: {{data.name}}</p>
The data will contain single data, so when I change data I want above template to append instead of replacing it. So the output will be like this:
ID: 1, Name: John
ID: 2, Name: Michael
ID: 3, Name: Abraham
I also want to handle onClick when the user tap on any name above.
How can I achieve this?
Ok i got it ! try this
angular.module('myApp', [])
.controller('myController', function($scope, $interval) {
var count = 0;
var addPerson = function() {
count++
$scope.person = {
name: 'person' + count,
age: 20 + count
};
};
var timer = $interval(addPerson, 1000);
})
.directive('myDirective', function($compile) {
return {
restrict: "A",
transclude: true,
scope: "=",
link: function(scope, element, attrs) {
scope.handleClick = function(event) {
console.log(event.target.innerText)
}
scope.$watch(function() {
return scope.person;
}, function(obj) {
var elementToRender = angular.element('<div ng-click="handleClick($event)">' + obj.name + '</div>');
function renderDiv() {
return elementToRender
}
element.append(renderDiv());
$compile(elementToRender)(scope);
}, false);
}
};
});
template
<div ng-app="myApp" ng-controller="myController">
<div my-directive data="{{person}}"></div>
</div>
I neeed to pass a value from this part of the code in my directive to a controller, but not sure how to achieve that:
if (!scope.multiple) {
scope.model = value;
console.log(scope.model);
return;
}
I get the value in the console.log, I just don't know how to pass it to the controller.
This is the complete directive:
angular.module('quiz.directives')
.directive('fancySelect', function($rootScope, $timeout) {
return {
restrict: 'E',
templateUrl: 'templates/directives/fancySelect.html',
scope: {
title: '#',
model: '=',
options: '=',
multiple: '=',
enable: '=',
onChange: '&',
class: '#'
},
link: function(scope) {
scope.showOptions = false;
scope.displayValues = [];
scope.$watch('enable', function(enable) {
if (!enable && scope.showOptions) {
scope.toggleShowOptions(false);
}
});
scope.toggleShowOptions = function(show) {
if (!scope.enable) {
return;
}
if (show === undefined) {
show = !scope.showOptions;
}
if (show) {
$rootScope.$broadcast('fancySelect:hideAll');
}
$timeout(function() {
scope.showOptions = show;
});
};
scope.toggleValue = function(value) {
if (!value) {
return;
}
if (!scope.multiple) {
scope.model = value;
console.log(scope.model);
return;
}
var index = scope.model.indexOf(value);
if (index >= 0) {
scope.model.splice(index, 1);
}
else {
scope.model.push(value);
}
if (scope.onChange) {
scope.onChange();
}
};
scope.getDisplayValues = function() {
if (!scope.options || !scope.model) {
return [];
}
if (!scope.multiple && scope.model) {
return scope.options.filter(function(opt) {
return opt.id == scope.model;
});
}
return scope.options.filter(function(opt) {
return scope.model.indexOf(opt.id) >= 0;
});
};
$rootScope.$on('fancySelect:hideAll', function() {
scope.showOptions = false;
});
}
};
});
Updated
I tried to do as suggested in the answers by #Zidane and defining my object first in the controller like this:
$scope.year = {};
var saveUser = function(user) {
$scope.profilePromise = UserService.save(user);
console.log($scope.year);
This is the template:
<fancy-select
title="Klassetrinn"
model="year"
options="years"
enable="true"
on-change="onChangeYears()"
active="yearsActive"
name="playerYear"
form-name="registerForm"
>
</fancy-select>
But I got an empty object in that case.
When I define my objects like this I get the right value in the controller but in the view the title is not being displayed anymore:
$scope.search = {
years: []
};
var saveUser = function(user) {
$scope.profilePromise = UserService.save(user);
console.log($scope.search.years);
<fancy-select
title="Klassetrinn"
model="search.years"
options="years"
enable="true"
on-change="onChangeYears()"
active="yearsActive"
name="playerYear"
form-name="registerForm"
>
</fancy-select>
As you defined an isolated scope for your directive like this
scope: {
...
model: '=',
...
},
you give your directive a reference to an object on your controller scope.
Declaring the directive like <fancy-select model="myModel" ....></fancy-select> you pass your directive a reference to scope.myModel on your controller. When you modify a property on the scope.model object in your directive you automatically modify the same property on the scope.myModel object in your controller.
So you have to do
myApp.controller('myController', function($scope) {
...
$scope.myModel = {};
...
}
in your controller and in your directive just do
if (!scope.multiple) {
scope.model.value = value;
return;
}
Then you can get the value in your controller via $scope.myModel.value.
For clarification: You have to define an object on your controller and pass the directive the reference for this object so that the directive can follow the reference and doesn't mask it. If you did in your directive scope.model = 33 then you would just mask the reference passed to it from the controller, which means scope.model wouldn't point to the object on the controller anymore. When you do scope.model.value = 33 then you actually follow the object reference and modify the object on the controller scope.
you can use services or factories to share data between your angular application parts, for example
angular.module('myapp').factory('myDataSharing', myDataSharing);
function myDataSharing() {
var sharedData = {
fieldOne: ''
};
return {
setData: setData,
getData: getData,
};
function setData(dataFieldValue) {
sharedData.fieldOne = dataFieldValue;
};
function getData() {
sharedData.fieldOne
};
directive:
myDataSharing.setData(dataValue);
controller:
angular.module('myapp').controller('myController' ['myDataSharing'], function(myDataSharing) {
var myDataFromSharedService = myDataSharing.getData();
}
I'm using TypeScript and creating a controller to pass rootScope data to a directive's link function, and while it can return the entire controller, I can't access the controller's attributes. I'm probably missing something pretty obvious, but just can't see it.
Directive
class AnnualPerfController {
width: any;
height: number;
fontSize: number;
factsheet: boolean;
values: number[];
constructor(private scope: ng.IScope, rootScope: ng.IRootScopeService) {
rootScope.$on('DataUpdated', () => {
this.factsheet = window.location.pathname.indexOf('factsheet') > -1;
this.height = this.factsheet ? 260 : 400;
this.width = this.factsheet ? 250 : null;
this.fontSize = this.factsheet ? 9 : 11;
this.values = [
rootScope['annualizedPerf']['y1_value'],
rootScope['annualizedPerf']['y3_value'],
rootScope['annualizedPerf']['y5_value'],
rootScope['annualizedPerf']['y10_value']
];
});
}
}
AnnualPerfController.$inject = ['$scope', '$rootScope', "$element"];
export function annualPerformance(): ng.IDirective {
return {
restrict: 'C',
replace: true,
controller: AnnualPerfController,
controllerAs: 'annualPerformance',
template: '<div id="annual" style="margin: 0 auto">not working</div>',
scope: {},
link: (scope, element, attr, ctrl) => {
console.log('values', scope.$eval(ctrl['values']));
console.log('ctrl', ctrl);
}
}
}
What's interesting is the link function; the first console.log returns undefined, while the second one returns the whole controller like so:
Which is exactly what I want. So how do I access the values key and its value, for instance?
As a caveat, this is data that is returned from a promise (if it helps)
Below is my plnkr
http://plnkr.co/edit/f6LYS2aGrTXGkZ3vrIDD?p=preview
I have issue on Search Page
angular.module('plexusSelect', []).directive('plexusSelect', ['$ionicModal',
function($ionicModal) {
// Runs during compile
return {
scope: {
'items': '=',
'text': '#',
'textIcon': '#',
'headerText': '#',
'textField': '#',
'textField2': '#',
'valueField': '#',
'callback': '&'
},
require: 'ngModel',
restrict: 'E',
templateUrl: 'templates/plexusSelect.html',
link: function($scope, iElm, iAttrs, ngModel) {
if (!ngModel) return; // do nothing if no ng-model
$scope.allowEmpty = iAttrs.allowEmpty === 'false' ? false : true;
$scope.defaultText = $scope.text || '';
$ionicModal.fromTemplateUrl('plexusSelectItems.html', {
'scope': $scope
}).then(function(modal) {
$scope.modal = modal;
$scope.modal['backdropClickToClose'] = false;
});
$scope.showItems = function($event) {
$event.preventDefault();
$scope.modal.show();
};
$scope.hideItems = function() {
$scope.modal.hide();
};
/* Destroy modal */
$scope.$on('$destroy', function() {
$scope.modal.remove();
});
$scope.viewModel = {};
$scope.clearSearch = function() {
$scope.viewModel.search = '';
};
/* Get field name and evaluate */
$scope.getItemName = function(field, item) {
return $scope.$eval(field, item);
};
$scope.validateSingle = function(item) {
$scope.text = $scope.$eval($scope.textField, item) + ($scope.textField2 !== undefined ? " (" + $scope.$eval($scope.textField2, item) + ")" : "");
$scope.value = $scope.$eval($scope.valueField, item);
$scope.hideItems();
if (typeof $scope.callback == 'function') {
$scope.callback($scope.value);
}
ngModel.$setViewValue($scope.value);
};
$scope.$watch('text', function(value) {
if ($scope.defaultText === value) $scope.placeholder = 'placeholderGray';
else $scope.placeholder = 'placeholderBlack';
});
}
};
}
])
Where in I have reference http://code.ionicframework.com/1.0.0-beta.14/js/ionic.bundle.js ionic bundle than my second directive search filter will stop working but at the same time, if I reference http://code.ionicframework.com/1.0.0-beta.1/js/ionic.bundle.js it works search filter in both directive.
In beta.14 angularjs 1.3 is used and in beta.1 angularjs 1.2
So somebody told me that it can be the migration issue, But I check angularjs migration documentation but I could not find anything. Kindly somebody help me what can be the issue.
Problem:
This is due to the following breaking change in Angular 1.3.6.
Excerpt:
filterFilter: due to a75537d4,
Named properties in the expression object will only match against
properties on the same level. Previously, named string properties
would match against properties on the same level or deeper.
...
In order to match deeper nested properties, you have to either match
the depth level of the property or use the special $ key (which still
matches properties on the same level or deeper)
In the first use of your directive items have the following structure:
[
{ property: 'Value' }
]
And in your second use:
[
{ Destination: { property: 'Value' } }
]
Sadly a bug fix that you probably need wasn't introduced until 1.3.8:
filterFilter:
make $ match properties on deeper levels as well
(bd28c74c, #10401)
let expression object {$: '...'} also match
primitive items (fb2c5858, #10428)
Solution:
Use Ionic with AngularJS 1.3.8 or later.
Change your HTML to the following:
<label ng-repeat="item in items | filter: { $: viewModel.search }" ...
Initialize viewModel.search as an empty string:
$scope.viewModel = { search: '' };
Demo: http://plnkr.co/edit/ZAM33j82gT4Y6hqJLqAl?p=preview
I have a problem: I use Angular and I need to use pushMsg method but I don't know how can I call it, boxCtrl.pushMsg(msg) does not work.
app.directive("fileread", function (socket) {
return {
scope: {
fileread: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
var msg = { author: 'me', class: 'me' };
// WHAT HERE???????
});
}
}
});
boxCtrl = function (socket, $scope) {
this.messages = [];
}
boxCtrl.prototype = {
pushMsg: function (message) {
this.messages.push(message);
}
}
app.controller('boxCtrl', boxCtrl);
You create an isolated scope and pass it as an attribute:
app.directive("fileread", function (socket) {
return {
scope: {
fileread: "=",
pushMessage: "="
},
link: function (scope, element, attributes) {
element.bind("change", function (changeEvent) {
var msg = { author: 'me', class: 'me' };
scope.pushMessage(msg);
});
}
}
});
And in your HTML:
<div fileread="..." push-message="pushMsg">
Edit: your controller should be something like this:
app.controller('Ctrl', function($scope) {
$scope.messages = [];
$scope.name = function(msg) {
$scope.messages.push(msg);
$scope.$apply(); //I think you need this to update the UI (not sure though)
}
})