Assignment to scope isn't working in directive - angularjs

I have a directive below that makes a change in the scope element sections.data which i later assign to update the data on UI. However, this approach works in the function change_subnav but not in the switch case (added the comments to code). Why is this happening? The code to this change is same. Will appreciate any help here. Please let me know if I need to add more information.
Plunker- https://embed.plnkr.co/wmoJcQ/
.directive('selectSubnav', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attr) {
var change_subnav = function (subnav) {
if (scope.active_tab == 'user_exercises') {
var sections = {};
sections[subnav] = scope[scope.active_tab][subnav];
// this works
scope.$apply(function () {
$parse('sections.data').assign(scope.$parent, sections);
});
} else {
}
};
$(element).on('click', function () {
$(element).parent().children().removeClass('active');
$(element).addClass('active');
switch (attr.selectSubnav) {
case 'All':
// this doesn't work
scope.$apply(function () {
$parse('sections.data').assign(scope.$parent, scope[scope.active_tab]);
console.log(scope.sections.data);
});
break;
default:
change_subnav(attr.selectSubnav);
break;
}
});
}
}
})
UPDATE
.directive('selectSubnav', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attr) {
var currentScope = scope;
var change_subnav = function (subnav) {
if (scope.active_tab == 'user_exercises') {
var sections = {};
sections[subnav] = scope[scope.active_tab][subnav];
} else {
}
return sections;
};
$(element).on('click', function () {
$(element).parent().children().removeClass('active');
$(element).addClass('active');
var sections;
switch (attr.selectSubnav) {
case 'All':
// this doesn't work
sections = currentScope[currentScope.active_tab];
break;
default:
sections = change_subnav(attr.selectSubnav);
break;
}
currentScope.$apply(function () {
$parse('sections.data').assign(currentScope.$parent, sections);
});
});
}
}
})

You already have access to scope in your link function, so instead of updating scope.sections.data like this:
$parse('sections.data').assign(scope.$parent, sections);
You can simply do this:
$parse('sections.data').assign(scope, sections);
Or, better yet just update it directly, like this:
scope.sections.data = sections;

Related

AngularJS, How to call a function in component from Directive?

Thanks in advance, Basically in app component a timer which should be start when I call from a directive, Please let me know how I can call? I tried some method but didn't helpful. I'm new in angular js Please guide me.Thanks Here is the Js File code.
var app = angular.module('quizApp', []);
app.directive('quiz', function (quizFactory) {
return {
restrict: 'AE',
scope: {},
templateUrl: '/Home/Dashboard',
link: function (scope, elem, attrs) {
//Calling function cmpTimer(); in Component
};
}
}
});
app.component('cmpTimer', {
bindings: {
cmpFrom: '<',
cmpUnit: '#'
},
template: '<div class="timer">{{$ctrl.timeRemaining}}</div>',
controller: function ($interval) {
var vm = this, ONE_SECOND = 1000, timerInterval;
function startTimer() {
var END_TIME = vm.getEndTime();
function update() {
vm.timeRemaining = moment((moment(END_TIME) - moment())).format("HH:mm:ss");
}
timerInterval = $interval(function () {
if (moment() > moment(END_TIME)) return vm.stopTimer();
update();
}, ONE_SECOND);
update();
}
vm.getEndTime = function () {
return moment().add(vm.cmpFrom, vm.cmpUnit);
}
vm.stopTimer = function () {
alert('Time\'s up!');
vm.$onDestroy();
}
vm.$onDestroy = function () {
$interval.cancel(timerInterval);
}
vm.$onInit = startTimer;
}
});

How to redisplay directive after Javascript function executes

I have an AngularJS Directive defined in a Javascript file that looks like this:
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
var directive = {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope: true
};
return directive;
function link(scope, element, attrs) {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
}
}
})();
and in the template URL of the directive there is a button that calls a Javascript function which looks like this:
function upload(files) {
var formData = new FormData();
angular.forEach(files, function (file) {
formData.append(file.name, file);
});
return fileManagerClient.save(formData)
.$promise
.then(function (result) {
if (result && result.files) {
result.files.forEach(function (file) {
if (!fileExists(file.name)) {
service.files.push(file);
}
});
}
appInfo.setInfo({ message: "files uploaded successfully" });
return result.$promise;
},
function (result) {
appInfo.setInfo({ message: "something went wrong: " +
result.data.message });
return $q.reject(result);
})
['finally'](
function () {
appInfo.setInfo({ busy: false });
service.status.uploading = false;
});
}
Once I select files for upload and click the upload button I need to reload the directive or somehow get it back to it's initial state so I can upload additional files. I'm relatively new to AngularJS and I'm not quite sure how to do this. Any help is much appreciated.
Thanks,
Pete
You just need to create a reset method. Also, you may want to call the parent controller function.
Using answer from this
ngFileSelect.directive.js
...
.directive("onFileChange",function(){
return {
restrict: 'A',
link: function($scope,el){
var onChangeHandler = scope.$eval(attrs.onFileChange);
el.bind('change', onChangeHandler);
}
}
...
fileUploader.directive.js
(function () {
'use strict';
angular
.module('ooApp.controllers')
.directive('fileUploader', fileUploader);
fileUploader.$inject = ['appInfo', 'fileManager'];
function fileUploader(appInfo, fileManager) {
return {
link: link,
restrict: 'E',
templateUrl: 'views/directive/UploadFile.html',
scope:{
onSubmitCallback: '&',
onFileChange: '&'
}
};
function link(scope, element, attrs) {
scope.reset = reset;
scope.fileChange = fileChange;
reset();
function reset() {
scope.hasFiles = false;
scope.files = [];
scope.upload = fileManager.upload;
scope.appStatus = appInfo.status;
scope.fileManagerStatus = fileManager.status;
if(typeof scope.onSubmitCallback === 'function') {
scope.onSubmitCallback();
}
}
function fileChange(file) {
if(typeof scope.onFileChange === 'function'){
scope.onFileChange(file);
}
}
}
}
})();
UploadFile.html
<form>
<div>
...
</div>
<input type="submit" ng-click="reset()" file-on-change="fileChange($files)" />Upload
</form>
parent.html
<file-uploader on-submit-callback="onUpload" on-file-change="onFileChange" ng-controller="UploadCtrl" />
upload.controller.js
...
$scope.onUpload = function() {
console.log('onUpload clicked %o', arguments);
};
$scope.onFileChange = function(e) {
var imageFile = (e.srcElement || e.target).files[0];
}
...

how to execute the statement after promise is executed?

I have used the following directory in my template
i want to change the model value of drop-down to id for it i have used as bellow
<md-select flex class="md-select-form" ng-model="education.degree" placeholder="Degree" save-id id="education.degree_id" ">
<md-option ng-value="degree" ng-repeat="degree in ['High School', 'Associates Degree', 'Bachelor Degree', 'Masters Degree', 'M.B.A', 'M.D', 'Ph.D', 'other']">{{degree}}</md-option>
</md-select>
.directory code
.directive('saveId', function(Profile){
return {
require: 'ngModel',
scope: {
id: '=',
requiredParam:'#'
},
link: function(scope, element, attrs, ngModel) {
console.log("initial loading");
// view --> model (change to string)
ngModel.$parsers.push(function(viewValue){
var keepGoing = true;
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob){
if(keepGoing) {
if(ob.degree == viewValue){
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
console.log("within promise"+scope.id); //it executes second
});
console.log(scope.id); //it executes first
return scope.id;
});
return scope.id;
}
};
})
Once if i try to return the value of ng-model in finally block it also not working
.directive('saveId', function(Profile){
return {
require: 'ngModel',
scope: {
id: '=',
requiredParam:'#'
},
link: function(scope, element, attrs, ngModel) {
console.log("initial loading");
// view --> model (change to string)
ngModel.$parsers.push(function(viewValue){
// var id = -1;
var keepGoing = true;
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob){
if(keepGoing) {
if(ob.degree == viewValue){
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
}).finally(function(res){
console.log(scope.id);
return scope.id;
});
});
return scope.id;
}
};
})
i have used Profile.getDegreeList() service to assign the id to relevant drop-down element by using ngModel.$parsers.push(). The problem is in case of service usage before promise going to complete it returns the previous assigned id .
i want to prevent it and need to return the promise id.
how to solve this issue please help?
You can use finally method which will be executed after executing promise.
Profile.getDegreeList()
.success(function(data)
{
angular.forEach(data, function(ob)
{
if (keepGoing)
if (ob.degree == viewValue) {
scope.id = ob.degree_id;
keepGoing = false;
}
});
console.log("within promise"+scope.id);
})
.finally(function(res)
{
// your code
}
);
Here you go first make a call and then use parsers.
Profile.getDegreeList().then(function(data) {
angular.forEach(data, function(ob) {
if (keepGoing) {
if (ob.degree == viewValue) {
scope.id = ob.degree_id;
keepGoing = false;
}
}
});
ngModel.$parsers.push(function(viewValue) {
var keepGoing = true;
return scope.id ;
});
});
See other links too for more guidance.

How to pass `controllers` status to `directive` template function?

Accoding to the current page, i need to change the template. my question is, how to pass the current page from controller to directives template method?
here is my try:
var myApp = angular.module('myApp', []);
myApp.controller('main', function ($scope) {
$scope.template = "homePage";
});
var getTemplate = function (page) { //i need $scope.template as params
if (page == "homePage") {
return "<button>One Button</button>"
}
if (page == "servicePage") {
return "<button>One Button</button><button>Two Button</button>"
}
if (page == "homePage") {
return "<button>One Button</button><button>Two Button</button><button>Three Button</button>"
}
}
myApp.directive('galleryMenu', function () {
return {
template : getTemplate(template), //$scope.template need to pass
link : function (scope, element, attrs) {
console.log(scope.template);
}
}
})
Live Demo
UPDATE
I am trying like this, but still getting error. what is the correct way to inject the $route to directive?
var galleryMenu = function ($route, $location) {
return {
template : function () {
console.log($route.current.className); //i am not getting!
},
link : function () {
}
}
}
angular
.module("tcpApp", ['$route', '$location'])
.directive('galleryMenu', galleryMenu);
You can call $routeParams on your directive declaration, to use it inside the template function.
myApp.directive('galleryMenu', ['$routeParams', function($routeParams) {
return {
template: function () {
var page = $routeParams.page || 'homePage', // Define a fallback, if $routeParams doesn't have 'page' param
output;
switch (page) {
case "servicePage":
output = "<button>One Button</button><button>Two Button</button>";
break;
default:
case "homePage":
output = "<button>One Button</button>";
/*
NOTE: Or this other, it was confusing to tell which one to use
output = "<button>One Button</button><button>Two Button</button><button>Three Button</button>";
*/
break;
}
return output;
},
link: function(scope, element, attrs) {
/* ... */
}
}
}]);
Edit 1:
If you are using ui-router switch from $routeParams to $stateParams.
You need to get current url from $state.current and can pass into the directive with the help of templateProvider.
myApp.directive('galleryMenu', function () {
return {
templateProvider : getTemplate(template), //$scope.template need to pass
link : function (scope, element, attrs) {
console.log(scope.template);
}
}
from getTemplate you can return $state.current. hope so it'll help you.

AngularJs: Run function when any element in section is blurred

I've created the following directive:
.directive('onSectionBlur', function ($parse) {
return {
restrict: 'A',
controller: function ($scope, $element, $attrs) {
$element.focusout(function (event) {
if (!jQuery.contains($element[0], event.relatedTarget)) {
$scope.$apply($parse($attrs.onSectionBlur)($scope));
}
});
}
};
})
My goal here is if a user tabs out of a section of a form (or clicks elsewhere), I want to display a read-only version of that data: http://jsfiddle.net/uZBXw/3/
So this works from what I can tell, but I feel like I was just mashing buttons on this line:
$scope.$apply($parse($attrs.onSectionBlur)($scope));
Is this the correct way to run code and wire it into the angular lifecycle?
I think you should use an isolated scope with an attribute marked with &. This will give you access to a function that will run on the parent scope and is the exact use case of what you're trying to do.
app.directive('onSectionBlur', function () {
return {
restrict: 'A',
scope: {
'notify': '&onSectionBlur' // reuse the directive name for easier handling
},
link: function (scope, element) {
element.on('focusout', function (evt) {
if (!angular.element.contains(element[0], evt.relatedTarget)) {
scope.$apply(scope.notify); // let $apply call the notify-callback
}
});
}
};
});
demo: http://jsbin.com/diwetaje/1/
from the Developer Guide:
Best Practice: use &attr in the scope option when you want your directive to expose an API for binding to behaviors.
I was having issues with clicking on various items in the section (i.e. checkbox labels), so if anyone else runs across this issue I've added a potential enhancement to Yoshi's version:
.directive('onSectionBlur', function ($document) {
return {
restrict: 'A',
scope: {
'notify': '&onSectionBlur'
},
link: function (scope, element) {
var hasFocus = false;
element.on('focusin', function (evt) {
hasFocus = true;
});
$document.on('click focusin', function (evt) {
if (hasFocus && !angular.element.contains(element[0], evt.target)) {
hasFocus = false;
scope.$apply(scope.notify);
}
});
}
};
});
EDIT: Here's the butchered up version I ended up with, that takes into account buttons that weren't clickable (if they were outside the section and below it) as well as not firing the event if the user has a modal window open:
link: function (scope, element) {
var hasFocus = false;
var lostFocus = function () {
hasFocus = false;
scope.$apply(scope.notify);
};
element.on('focusin', function (evt) {
hasFocus = true;
});
element.on('keydown', function (evt) {
if (hasFocus && evt.keyCode == 9) {
//Using timeout to give the browser time to process what it should have been doing (i.e. focusing next item)
if (evt.shiftKey && element.find(':focusable:first').is(evt.target)) {
$timeout(lostFocus);
} else if (element.find(':focusable:last').is(evt.target)) {
$timeout(lostFocus);
}
}
});
var docHandler = function (evt) {
//If the click came from inside of a modal window, ignore it
if (angular.element(evt.target).closest('.modal').length == 0) {
if (hasFocus && !angular.element.contains(element[0], evt.target)) {
lostFocus();
}
}
};
$document.on('click', docHandler);
scope.$on('$destroy', function () {
$document.off('click', docHandler);
});
}

Resources