How to get value from directive in Angular - javascript

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

Related

Property in directive has different value then the controller one

Controller:
constructor($scope) {
this.$scope = $scope;
this.counter = 0;
this.playerCells = [];
this.$scope.$watch(() => this.playerCells, this.updateCounter(), true)
}
updateCounter() {
return () => {
this.counter = 0;
this.playerCells.forEach((item) => {
if ( item.isShip === true ) {
this.counter++;
console.log("Ctrl counter "+this.counter)
}
})
}
}
Directive
export function fieldDir() {
return {
restrict: 'E',
template: require(...),
scope: {
cells: '=',
counter: '=',
},
link: function($scope) {
$scope.selectShip = function(item) {
if ( item.isShip === false) {
item.isShip = true;
} else {
item.isShip = false;
}
console.log("Dir counter " + $scope.counter)
}
}
}
}
<field-dir cells="vm.playerCells" counter="vm.counter"></field-dir>
I'm looking for the value of counter in both the controller and the directive. The console.log returns the following values after the $watch is being triggered for the first time:
Directive log: Dir counter 0
Controller log: Ctrl counter 1
I'm guessing the directive is being instantiated first by the console.log order and is taking the initial value of this.counter before $watch triggers the listener function.
Is there any way I can fix this or is there a better alternative to what I'm doing now ?

$scope in AngularJS modal doesn't pass data into the template

I am new to AngularJS. I have created the following controller that display a list of results and that opens a modal when a specific button is clicked:
angular.
module('panelList')
.component('panelList', {
templateUrl: '/panel-list/panel-list.template.html',
controller: ['Panel', 'PanelSelection', '$scope', '$location', '$uibModal',
function PanelListController(Panel, PanelSelection, $scope, $location, $uibModal) {
$scope.maxAbv = 2;
$scope.minAbv = 12;
$scope.maxIbu = 0;
$scope.minIbu = 100;
this.allPanelsRetrieved = (index, before, filterParams) => {
let allPanels = before;
const params = Object.assign({},
{ page: index, per_page: 80 },
filterParams);
Panel.query(params).$promise.then(data => {
if (data.length > 0) {
allPanels.push(...data);
return this.allPanelsRetrieved(index+1, allPanels, filterParams);
} else {
return allPanels;
}
});
return allPanels;
};
$scope.getPanels = () => {
const filterParams = {};
filterParams.abv_lt = $scope.minAbv;
filterParams.abv_gt = $scope.maxAbv;
filterParams.ibu_lt = $scope.minIbu;
filterParams.ibu_gt = $scope.maxIbu;
$scope.currentPagePanels = this.allPanelsRetrieved(1,[], filterParams);
};
$scope.showDetails = (panelSelected) => {
PanelSelection.setPanelSelected(panelSelected);
$uibModal.open({
component: "panelDetail",
scope: $scope,
bindToController: true,
})
};
}]
});
The controller for the modal is specified here:
angular.
module('panelDetail').
component('panelDetail', {
templateUrl: '/panel-detail/panel-detail.template.html',
controller: ['PanelSelection', '$scope','$uibModal',
function PanelDetailController(PanelSelection, $scope, $uibModal, $uibModalInstance) {
$scope.ok = () => {
$uibModalInstance.close();
};
let panelSelected = PanelSelection.getPanelSelected();
$scope.panel = panelSelected;
console.log(panelSelected);
$scope.foodPairings = panelSelected.food_pairing.join(", ");
$scope.allIngredients = this.getFormattedIngredients(panelSelected.ingredients);
$scope.method = this.getFormattedMethod(panelSelected.method);
this.getFormattedIngredients = (ingredients) => {
const listOfIngredients = [];
Object.keys(ingredients).forEach(key => {
if(Array.isArray(ingredients[key])){
for(let ingredient of ingredients[key]){
listOfIngredients.push(
`- ${ingredient.name} ${key} (${ingredient.amount.value} ${ingredient.amount.unit})`
.concat(ingredient.add != undefined ? ', added in the '+ingredient.add:'',
ingredient.attribute != undefined ? ', attribute: '+ingredient.attribute:'','.')
);
}
}else{
listOfIngredients.push(`- ${ingredients[key]} ${key}.`);
}
});
return listOfIngredients;
};
$scope.getFormattedMethod = (method) => {
const listOfMethodProcedures = [];
Object.keys(method).forEach(key => {
if(Array.isArray(method[key])){
for(let methodProcedure of method[key]){
listOfMethodProcedures.push(
`- ${key} at ${methodProcedure.temp.value} ${methodProcedure.temp.unit} `
.concat(methodProcedure.duration != undefined ? 'for '+methodProcedure.duration +' min.' : '.')
);
}
}else{
listOfMethodProcedures.push(`- ${key}.`);
}
});
return listOfMethodProcedures;
};
}
]
});
The modal is open correctly but the values inside are not taken from the scope, as they should, but they are displayed as {{value}}. In few words, the $scope passed doesn't act as scope. Moreover I get the following error:
TypeError: this.getFormattedIngredients is not a function
at new PanelDetailController
Where the error may be? How to pass successfully a scope from one controller to another for modal?
Instead of scope: $scope pass values using
resolve: { scope: $scope }
You are calling the getFormattedIngredients function before it gets declared. So this is not a $scope issue. You need to declare the function before it gets called. One way to solve such an issue is going with the angular Styleguide provided by John Papa. Angular Styleguide
Assign your function at the top of your Component/Controller/Service and use function expressions instead of function declarations.
function PanelDetailController(PanelSelection, $scope, $uibModal,$uibModalInstance) {
this.getFormattedIngredients = getFormattedIngredients;
// You can call your function from here without getting an error
// Other Code..
function getFormattedIngredients() {}

Directive to Hide/Show elements based on Session Service - AngularJS

Following up this answer, I was trying to build two directives to allow/deny elements to be visible by the end user.
angular.module('app.directives').directive('deny', ['SessionTool', function (SessionTool) {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
scope.$watch(SessionTool.user, function (value, oldValue) {
var list = attrs.deny.split(',');
if (SessionTool.hasAnyRole(list))
return elem.hide();
return elem.show();
});
}
}
}]);
My problem is that when I do make the logon, the $watch function is not being called again to make the invisible element appear.
A resume of my SessionTool is listed below.
angular.module('app.tools').factory('SessionTool', ['$cookies', function ($cookies) {
var _cookieKey = 'user';
return {
user: {},
init: function () {
var u = $cookies.get(_cookieKey);
try {
u = angular.fromJson(u);
this.user = u;
} catch (e) {
console.log('invalid json');
}
},
login: function (u) {
this.user = u;
$cookies.putObject(_cookieKey, u, {path: '/'}); // #TODO encrypt the whole JSON before saving it to cookies.
},
...
};
}]);
Anybody could point out why the $watch isn't being fired?
Thanks in advance.
I think that your directive is currently watching an anonymous variable SessionTool.user in your directive scope not the actual variable. I suggest going with this approach instead.
angular.module('app.tools').factory('SessionTool', ['$cookies','$rootScope', function ($cookies) {
var _cookieKey = 'user';
var _user = {};
return {
setUser: function(user) {
_user = user;
$rootScope.$broadcast('SessionToolChange');
}
getUser: function() {
return _user;
}
init: function () {
var u = $cookies.get(_cookieKey);
try {
u = angular.fromJson(u);
this.user = u;
} catch (e) {
console.log('invalid json');
}
},
login: function (u) {
this.user = u;
$cookies.putObject(_cookieKey, u, {path: '/'}); // #TODO encrypt the whole JSON before saving it to cookies.
},
...
};
}]);
angular.module('app.directives').directive('deny', ['SessionTool', function (SessionTool) {
return {
restrict: 'A',
controller: function (scope, elem, attrs) {
scope.$on('SessionToolChange', function (value, oldValue) {
// get the user and do your stuff.
});
}
}
}]);

AngularJS share data bettween parent and child scope directives

I have a widget like directive called waComments, it loads components via a RESTful service and displays them. In my view I'm using ng-repeat to loop over them and to render them with a button that if pressed Shows a new reply to form. This his handled by the waCommentsReply directive. One waComments widget has many child directives of type waCommentsReply. When the form is filled and submitted I want to add the new comment on top of my comments list. So both directives have to share the comments data.
I've tried to implement this here Sharing data between directives but without much success, the comment data is not updated when I add a new comment. I see that the RESTful API calls work and the data is returned, so this is not an issue.
Why is my implementation of Sharing data between directives not working in my case?
waCommentsReply directive:
waFrontend.directive('waCommentsReply', ['$rootScope', 'Comment', 'WaFormValidation', 'WaCommentStore', function($rootScope, Comment, WaFormValidation, WaCommentStore) {
return {
restrict: 'E',
templateUrl: '/stubs/comment-form.html',
transclude: true,
scope: {
replyTo: '#replyTo',
replyFormList: '=replyFormList',
loggedIn: '#loggedIn',
model: '#model',
id: '#id',
cancelButton: '#cancelButton'
},
controller: function($scope) {
$scope.comments = WaCommentStore;
if ($scope.cancelButton == undefined) {
$scope.cancelButton = true;
} else {
$scope.cancelButton = false;
}
$scope.comment = $scope.commentForm = {
Comment: {
author_name: '',
body: '',
model: $scope.model,
foreign_key: $scope.id,
parent_id: $scope.replyTo
}
};
$scope.$watch('replyFormList', function (newValue, oldValue) {
if (newValue) {
$scope.replyFormList = newValue;
}
});
if ($scope.loggedIn == undefined) {
$scope.loggedIn = false;
}
/**
* Handles the submission and response of a reply
*
* #return void
*/
$scope.reply = function() {
Comment.add($scope.comment).then(function(result) {
if (result.status == 'fail' || result.validation != undefined) {
$scope.validationErrors = result.validation;
WaFormValidation.validate(result.validation, $scope.commentForm);
} else if (result.status == 'success') {
//$scope.$parent.comments.unshift(result.data.comment);
//$scope.comments.unshift(result.data.comment);
$scope.comments.comments.unshift(result.data.comment);
//WaCommentStore.append($scope.model, $scope.id, result.data.comment);
$scope.comments, $scope.id, result.data.comment
$scope.comment = {};
$scope.replyFormList[$scope.replyTo] = false;
}
});
};
$scope.close = function() {
$scope.comment = {};
if ($scope.replyFormList[$scope.replyTo] != undefined) {
$scope.replyFormList[$scope.replyTo] = false;
}
}
}
};
}]);
WaCommentStore directive:
waFrontend.factory('WaCommentStore', function() {
return {
comments: []
};
});
waComments directive:
waFrontend.directive('waComments', ['$rootScope', 'Comment', 'WaCommentStore', function($rootScope, Comment, WaCommentStore) {
return {
restrict: 'E',
templateUrl: '/stubs/comments.html',
scope: {
model: '#commentModel',
id: '#commentFk'
},
controller: function($scope) {
$scope.comments = WaCommentStore;
$scope.loaded = false;
$scope.loadedMore = true;
$scope.currentPage = 1;
$scope.loggedIn = false;
$scope.paging = {};
$scope.replyFormList = {};
Comment.comments($scope.model, $scope.id).then(function(result) {
$scope.comments.comments.push.apply($scope.comments.comments, result.data.comments);
$scope.loggedIn = result.data.loggedIn;
$scope.paging = result.paging.Comment;
$scope.loaded = true;
});
$scope.loadMore = function() {
$scope.loadedMore = false;
if ($scope.paging.nextPage == false) {
//return false;
}
var options = {
page: $scope.paging.page + 1
};
Comment.comments($scope.model, $scope.id, options).then(function(result) {
$scope.comments.comments.push.apply($scope.comments.comments, result.data.comments);
$scope.paging = result.paging.Comment;
$scope.loadedMore = true;
});
};
$scope.submitComment = function() {
//alert($scope.author_name + $scope.body);
};
$scope.reply = function(replyId) {
$scope.replyFormList[replyId] = true;
}
}
};
}]);
since in both directive you defined scope: {} basically it means you defined those directives to use isolated scope.
with isolated scope, a scope/directive can't see what is in the parent scope.
however parent scope, can be affected by the child scope changes with 2 way binding definition.
https://docs.angularjs.org/guide/scope
try changing the shared data like this
waFrontend.factory('WaCommentStore', function() {
var comments = [];
var getComments = function() { return comments; }
var setComments = function(data) { comments = data; }
return {
getComments : getComments ,
setComments : setComments
};
});
I wanted to put it as a comments, but it would have been difficult to understand for you.
Please let me know if this works, else I will delete this answer.

AngularJS watching service variable from directive, how to correctly implement factory?

I want to watch angular factory variable from inside directive, and act upon change.
I must be missing something fundamental from Javascript, but can someone explain, why approach (1) using inline object works, and approach (2) using prototyping does not?
Does prototype somehow hide user variable scope from angular $watch?
How can i make this code more clean?
(1):
Plunkr demo
angular.module('testApp', [
])
.factory('myUser', [function () {
var userService = {};
var user = {id : Date.now()};
userService.get = function() {
return user;
};
userService.set = function(newUser) {
user = newUser;
};
return userService;
}])
.directive('userId',['myUser',function(myUser) {
return {
restrict : 'A',
link: function(scope, elm, attrs) {
scope.$watch(myUser.get, function(newUser) {
if(newUser) {
elm.text(newUser.id);
}
});
}
};
}])
.controller('ChangeCtrl', ['myUser', '$scope',function(myUser, $scope) {
$scope.change = function() {
myUser.set({id: Date.now()});
};
}]);
(2):
Plunkr demo
angular.module('testApp', [
])
.factory('myUser', [function () {
var user = {id : Date.now()};
var UserService = function(initial) {
this.user = initial;
}
UserService.prototype.get = function() {
return this.user;
};
UserService.prototype.set = function(newUser) {
this.user = newUser;
};
return new UserService(user);
}])
.directive('userId',['myUser',function(myUser) {
return {
restrict : 'A',
link: function(scope, elm, attrs) {
scope.$watch(myUser.get, function(newUser) {
//this watch does not fire
if(newUser) {
elm.text(newUser.id);
}
});
}
};
}])
.controller('ChangeCtrl', ['myUser', '$scope',function(myUser, $scope) {
$scope.change = function() {
myUser.set({id: Date.now()});
};
}]);
Case 1:
myUser.get function reference is called without a context (return user), and the user object is returned as a closure variable.
Case 2:
myUser.get function reference is called without a context (return this.user), and so this.user only just don't throw an error because you are not in "strict mode" where this is pointing to the window object, thus resulting in this.user being just undefined.
What you actually missed is the fact that giving myUser.get as a watcher check function is giving a reference to a function, which will not be applied to myUser as a context when used by the watcher.
As i remember angular watches only properties belonging to the object.
The watch function does this by checking the property with hasOwnProperty

Categories