Hi Im having an issue where im showing a mdDialog from Angular Material and using my directives controller as the controller of the dialog so i can call a specific function without having to pass stuff back and add in extra steps to the code. The function gets called successfully but the UI is not updated when the function successfully ends. Wondering if anyone can see where im going wrong with this.
Assume for now that the first if statement is true.
Dialog call
this.showImageUploadModal = function() {
$mdDialog.show({
clickOutsideToClose: true,
scope: $scope, // use parent scope in template
preserveScope: true, // do not forget this if use parent scope
templateUrl: 'app/directives/modals/upload-files-modal.html',
controller: MessagingController,
controllerAs: 'controller'
});
};
Function being called but not updating UI
this.addAttachment = function() {
console.log("sending attachment");
var ref = this;
var note = this.user.first_name + " has attached a file.";
if($state.current.name === 'inbox') {
MessagingService.createMessage(this.convo.id, note, this.userUploadedNoteFiles).then(
function success(response) {
console.log("Inbox attachment sent", response);
ref.convo.messages.push(response.data);
console.log(ref.convo.messages);
// ref.viewableNoteFiles = [];
},
function failure(response) {
$mdToast.show(
$mdToast.simple().
textContent("Failed to send the message please try again.").
theme('error-toast'));
}
);
} else if (this.notes === 'true') {
TicketingService.addNote($stateParams.id, note, this.userUploadedNoteFiles).then(
function success(response) {
console.log("Notes attachment sent", response);
ref.convo.messages.push(response.data);
// ref.viewableNoteFiles = [];
},
function failure(response) {
$mdToast.show(
$mdToast.simple().
textContent("Failed to send the message please try again.").
theme('error-toast'));
}
);
} else if(this.contractor === 'true') {
TicketingService.createMessage($stateParams.id, this.convo.id, note, this.userUploadedNoteFiles).then(
function success (response) {
console.log("Contractor attachment sent", response);
ref.convo.messages.push(response.data);
},
function failure () {
$mdToast.show(
$mdToast.simple().
textContent("Failed to upload the file attachments").
theme('error-toast'));
}
);
}
};
In the end i found i could achieve what i was looking for using Angulars $rootScope.$broadcast. to broadcast the return data back to the controller that needed it.
Im not sure this is the right way to do it but it works.
Related
I have been struggling with creating a system to allow articles to be retrieved and opened in a modal pop-up window. It has been implemented successfully using the Bootstrap modal, but due to some new requirements I need to convert to using the Angular UI Modal.
I think the issue is stemming from my handling of URL changes by Angular's $location.search(), but I can't pinpoint it.
Since adding the $uibModal.open() call, this infinite digest loop occurs whenever I click on an article, this launching the openModal function in my controller.
I will include my controller code and the error message I receive below. The two points of entry to the controller are near the bottom at the $rootScope.$on and $scope.$watch calls. They allow the modal to respond to changes in the URL.
The end goal is the ability to open an Angular UI modal when the URL changes, so that I can remove the URL params when the modal is dismissed.
Thanks for any help!
My controller:
(function () {
'use strict';
//Create the LinkModalController and bind it to the core app. This makes it always available.
angular
.module('app.core')
.controller('LinkModalController', LinkModalController);
LinkModalController.$inject = ['$location', '$q', '$rootScope', '$scope', '$uibModal', 'contentpartservice', 'logger'];
/* #ngInject */
function LinkModalController($location, $q, $rootScope, $scope, $uibModal, contentpartservice, logger) {
var vm = this;
/*--------------Variable Definitions--------------*/
vm.modalData = {};
vm.isModalLoading = true;
vm.selectedTab;
vm.urlHistory;
/*--------------Function Definitions--------------*/
vm.selectTab = selectTab;
vm.openModal = openModal;
/*Activate Controller*/
activate();
/*--------------Functions--------------*/
/*Announcement clicks are handled separately because the announcement data contains the full article*/
function handleAnnouncementClick(data) {
vm.modalData = data;
$("#announcementModal").modal();
return;
}
/*Set the active tab for the open modal*/
function selectTab(tab) {
vm.selectedTab = tab;
return;
}
/*Clicking an article of any content type should be funneled through this function. Eventually to be merged with handleSearchResultClick*/
function handleContentTypeClick(data) {
setUrl(data.id, data.contentType.value);
return;
}
function handleUrlParamsModalLaunch(data) {
console.log('launching modal');
/*Ensure modal is not displaying any data*/
vm.modalData = {};
vm.selectedTab = null;
/*Show modal loading screen*/
vm.isModalLoading = true;
var modalInstance = $uibModal.open({
templateUrl: 'app/modals/contentTypeModalTemplate.html',
controller: 'LinkModalController as vm',
});
/*Call the content service to return the clicked content article*/
contentpartservice.getContentItem(data.id, data.type).then(function (contentItem) {
if (contentItem) {
vm.isModalLoading = false;
vm.modalData = contentItem;
return;
} else {
closeModal("#contentPartModal").then(function () {
vm.isModalLoading = false;
logger.error('An error occurred while fetching content');
});
return;
}
}, function (error) {
closeModal("#contentPartModal").then(function () {
vm.isModalLoading = false;
logger.error('An error occurred while fetching content');
});
return;
});
}
/*Close a modal and return a promise object - This allows other code to be executed only after the modal closes*/
function closeModal(modalId) {
$(modalId).modal('hide');
var defer = $q.defer();
defer.resolve();
return defer.promise;
}
//Function to append information to the URL required to retrieve the displayed article
function setUrl(contentId, contentType) {
var urlParams = $location.search();
if (urlParams.q) {
$location.search({ q: urlParams.q, type: contentType, id: contentId });
} else {
$location.search({ type: contentType, id: contentId });
}
console.log($location.search());
return;
}
/*Route link click calls to handle different data structures*/
function openModal(data, context) {
switch (context) {
case 'urlParams':
handleUrlParamsModalLaunch(data);
break;
case 'announcement':
handleAnnouncementClick(data);
break;
case 'contentType':
handleContentTypeClick(data);
break;
default:
logger.error('An error occurred while fetching content');
}
return;
}
/*--------------Listeners--------------*/
/*Catch links click events broadcast from the $rootScope (shell.controller.js)*/
$rootScope.$on('openModal', function (event, data, context) {
vm.openModal(data, context);
return;
});
/*--------------Activate Controller--------------*/
function activate() {
/*Create a watcher to detect changes to the URL*/
$scope.$watch(function () { return $location.search() }, function () {
alert('url changed');
/*Wait for modals to render*/
var urlParams = $location.search();
if (urlParams.type && urlParams.id) {
vm.openModal(urlParams, 'urlParams');
}
/*Handle the inital page load. (Must wait until content is loaded to open modal). This code only runs once.*/
$rootScope.$on('$includeContentLoaded', function () {
alert('url changed first laod');
/*Wait for modals to render*/
var urlParams = $location.search();
if (urlParams.type && urlParams.id) {
vm.openModal(urlParams, 'urlParams');
}
});
}, true);
}
}
})();
The error message that was logged is a massive block of text, so I've pasted it into a Google Doc: https://docs.google.com/document/d/1esqZSMK4_Tiqckm-IjObqTvMGre2Ls-DWrIycvW5CKY/edit?usp=sharing
don't know if you have tried $locationProvider.html5Mode(true); in your app's config module. If you use jquery model to open the popup, it might have conflict between angular and jquery because jquery also watches the change on the url. I used to have similar issue like this.
I'm trying to communicate with server using following code.
$scope.Save = function () {
$scope.loading = true;
if ($scope.roleSetupModel.RoleID === "") {
// Save
//$scope.loading = true;
$http.post('/SecurityModule/RoleSetup/AddRole', JSON.stringify($scope.roleSetupModel))
.success(function (response) {
$scope.loading = false;
$('#addModal').modal('hide');
$scope.BindDataToGrid();
alert("Success");
toastr.success(response.Message);
})
.error(function (response) {
$scope.loading = false;
alert("Fail");
toastr.error(response.Message);
});
}
else {
$http.post('/SecurityModule/RoleSetup/EditRole',
JSON.stringify(convArrToObj($scope.roleSetupModel)))
.success(function (response) {
$('#addModal').modal('hide');
$scope.loading = false;
toastr.success(response.Message);
$scope.BindDataToGrid();
})
.error(function (data) {
$scope.loading = false;
toastr.error(response.Message);
});
}
}
But I'm getting wired behavior. The link is being hit everytime, I put a debugger there, but even before completing the request my page getting kind of refreshed making my model empty with out entering the success or error call back. I know a little about the promise object but couldn't make sense. What I'm doing wrong here?
My guess is you aren't binding $scope.Save() to ng-submit of <form> and have it being triggered by a submit button, or you have not followed the docs for forms
If form is set up with:
<form name="formScopeName" ng-submit="Save()" novalidate>
Your $http should work fine. However if you have action set on form angular interprets that as you want to submit without ajax nd use default browser process. So make sure action attribute doesn't exist.
Also note there is no reason to stringify the data yourself. This will be done automatically by $http internally. This could be causing error perhaps
You can also pass in $event for extra protection and then preventDefault. Generally this isn;t necessary however.
ng-submit="Save($event)"
JS
$scope.Save = function (event){
event.preventDefult();
.....
This is undoubtedly a stupid problem where I'm just doing something simple wrong.
I have a page with several directives, loading their templates and controllers. All of which is working fine except for this one.
Using the controller as model, this. is the same as $scope.. So in my controller I have:
var self = this;
this.states = { showControls: false, showVideo: false }
this.showVideo = function() { self.states.showVideo = true; }
this.showControls = function() { self.states.showControls = true; }
$scope.$on(Constants.EVENT.START_WEBCAM, self.showVideo)
$scope.$on(Constants.EVENT.VIDEO_SUCCESS, self.showControls)
In the view I have a button to reveal this part of the view and subsequently request access to your webcam. Clicking the button broadcasts an event with $rootScope.$broadcast from the parent controller.
When the user grants access to the webcam (handled in the directive's link function) it broadcasts another event the same way.
Both methods are triggered by listening with $scope.$on, and both methods fire as they should. However, the showVideo method successfully updates its associated state property, and the showControls method does not. What am I doing wrong?
Using the debug tool it looks like states.showControls is being set to true, but this change isn't reflected in the view, and adding a watcher to the states object doesn't detect any change at this point either. It does when I set showVideo.
EDIT
This part is in the directive:
if (Modernizr && Modernizr.prefixed('getUserMedia', navigator)) {
userMedia = Modernizr.prefixed('getUserMedia', navigator);
}
var videoSuccess = function(stream) {
// Do some stuff
$rootScope.$broadcast(Constants.EVENT.VIDEO_SUCCESS);
}
scope.$on(Constants.EVENT.START_WEBCAM, function() {
if (MediaStreamTrack && MediaStreamTrack.getSources) {
MediaStreamTrack.getSources(function(sourceInfo) {
var audio = null;
var video = null;
_.each(sourceInfo, function(info, i) {
if (info.kind === "audio") {
audio = info.id;
} else if (info.kind === "video") {
video = info.id;
} else {
console.log("random unknown source: ", info);
}
});
if (userMedia) { userMedia(getReqs(), videoSuccess, error); }
});
}
});
Requirement : To Show and Hide a div.
HTML
<div ng-show="IsSuccess">
My Div Content
</div>
HTML after page load
<div class="ng-hide" ng-show="false">
HTML after updated from controller (http post call)
<div class="ng-hide" ng-show="true">
ng-show is true but still class contains ng-hide
How to resolve this issue ?
For reference, below is my controller
myController.controller('AuthenticationController',
function AuthenticationController($scope, $location, authDataService, loginDuration) {
$scope.Login = {};
$scope.IsSuccess= false;
$scope.login = function () {
authDataService.authenticateUser($scope.Login, $scope).then(
function (status) {
if (status === 200) {
if ($scope.message == 'Login failed') {
$scope.IsSuccess= true;
}
else {
$scope.IsSuccess= false;
}
}
},
function (data) {
$scope.ErrorMessage = data.Message;
}
);
}
});
Because authDataService.authenticateUser is returning a promise that looks like it's outside of the angular context, angular doesn't know when the scope changes. In that situation, you need to add $scope.$apply()
if ($scope.message == 'Login failed') {
scope.IsSuccess= true;
}
else {
$scope.IsSuccess= false;
}
$scope.$apply();
** Edit: Extended Explanation **
Because you asked for more details about this, I'll try to explain a little further.
$scope.$apply() needs to be called when outside of the angular context. Here's what I mean by outside of the angular context:
$scope.login = function() {
// inside angular context
console.log('a');
setTimeout(function() {
// outside angular context
console.log('b');
$scope.hello = 'b';
// $scope.$apply() needs to be called
$scope.$apply();
}, 1000);
// inside angular context
console.log('c');
$scope.hello = 'c';
};
In this example, here's the output to the log:
a
c
// $scope.$apply() is assumed at this point
b
Angular knows it needs to adjust its bindings after the last line of $scope.login() is processed, and so $scope.$apply() is assumed then, but Angular doesn't know if you have any other callback functions that might be called later on through another context, another context being setTimeout or jQuery's $.ajax or $.Deferred, etc. If that different context modifies the $scope, then you need to call $scope.$apply() to manually update the Angular bindings.
If I am understanding your question correctly, I would change your HTML to show
<div ng-hide="IsSuccess">
My Div Content
</div>
and then in your angular file
$scope.login = function () {
if(<!-- logic to hide or show-->){
$scope.IsSuccess = false;
}else{
$scope.IsSuccess = true;
}
}
Hopefully this helps.
HTML code:
<button class="show-more-btn ng-hide" ng-show="hasMoreItemsToShow()" ng-click="showMoreItems()"">Show More</button>
Javascript code:
setTimeout(function(){ $('.show-more-btn').removeClass('ng-hide');
}, 3000);
When establishing a controller, and setting the $scope to use a factory method (for GETs and POSTs), during the page load process, my POSTs are fired. Below is an example of the code. To fix this, I wrapped the "POST" in a jQuery click event function and everything works smoothly. Below is the code.
In the controller (app.js):
demoApp.controller('SimpleController', function ($scope, simpleFactory) {
$scope.customers = [];
init();
function init() {
$scope.departments = simpleFactory.getDepartments();
}
// Works fine
$('#myButton').click(function () {
simpleFactory.postDepartments();
});
// When setting the "scope" of a controller, during page load the scope factory method is fired off!
// seems like a defect.
//$scope.addDepartment = simpleFactory.postDepartments();
});
So, what is going on here is that if I uncomment the $scope.addDepartment = ... on page load, the postDepartments() factory method is called. This is not the desired behavior. Here is how I have the Html Dom element wired:
<button id="myButton" data-ng-click="addDepartment()">Add Department</button>
So, if I uncomment, like I said above, it adds the department before the user even clicks the button. However, approaching it the jQuery way, there is no issue.
Is this a known bug? Is this the intended functionality? Also, see the factory below, maybe the problem is there?
demoApp.factory('simpleFactory', function ($http) {
var departments = [];
var factory = {};
factory.getDepartments = function () {
$http.get('/Home/GetDepartments').success(function (data) {
for (var i = 0; i < data.length; i++) {
departments.push({ desc: data[i].desc, id: data[i].id });
}
})
.error(function () {
$scope.error = "An Error has occured while loading posts!";
$scope.loading = false;
});
return departments;
};
factory.postDepartments = function () {
$http.post('/Home/PostDepartment', {
cName: 'TST',
cDescription: 'Test Department'
}).success(function (data) {
departments.push({ desc: 'Test Department', id: departments.length + 1 });
})
.error(function () {
$scope.error = "An Error has occured while loading posts!";
$scope.loading = false;
});
return departments;
};
return factory;
});
Try this:
$scope.addDepartment = function() {
return simpleFactory.postDepartments();
}
This will also allow you to pass in arguments in the future, should you decide to. The way you originally had it, you were both assigning the function and calling it at the same time.
Then, you can use it in ngClick:
<button ng-click="addDepartment()">Add Department</button>
Don't use the jQuery click method in your controller, it defeats the purpose of separating the concerns into models, views, controllers, etc. That's what directives are for.