I am refactoring existing code that I wrote (which is full of the $scope variable) to follow some best practices. Im trying to use 'this' instead as well as controllerAs syntax. I have the following html which works:
<div ng-hide="card.VIN == 0" class="col-md-7" id="cardAndInfoContainer">
<list-card card="card"></list-card>
</div>
This is my directive (only relevant parts, it's long):
scope: {
card: '='
},
controller: function ($scope, $controller) {
$scope.card = {
VIN: 25,
status: "fadfda",
image: "",
};
$scope.callServiceFunction = function (id) {
//call function in a service
Service.getInfo(id).then(function (data) {
$scope.card = data;
}, function (err) {
console.log(err);
})
}
},
//controllerAs: 'ctrl',
How can I refactor the code to follow the best practices above? Here is what I tried but it did not work:
-html unchanged
-in directive controller:
controllerAs: 'ctrl',
scope: {
card: '='
},
template: template,
controller: function ($scope, $controller) {
var ctrl = this;
this.card = {
VIN:0,
status: "fadfda",
image: ""
};
this.displayCard = function (id) {
Service.getInfo(id).then(function (data) {
$scope.card = data;
}, function (err) {
console.log(err);
})
}
return ctrl;
}
You left in $scope here:
Service.getInfo(id).then(function (data) {
$scope.card = data;
}, function (err) {
console.log(err);
})
Try
Service.getInfo(id).then(function (data) {
ctrl.card = data;
}, function (err) {
console.log(err);
});
One suggestion is to actually declare named functions and variable objects at bottom of controller
function displayCard(id) {
Service.getInfo(id).then(function (data) {
$scope.card = data;
}, function (err) {
console.log(err);
})
}
var card = {
VIN:0,
status: "fadfda",
image: ""
};
Then at top of controller bind all references to objects from bottom
controller: function ($scope, $controller) {
var ctrl = this;
ctrl.card = card;
ctrl.displayCard = displayCard;
The benefit is that you easily create a table of contents so to speak at the top of the component and all the business is down below
Related
I'm pretty new to angular js, currently, it's going well but now I have a question.
I have a template with a topnav and a contentpart. All with its own controller.
With a button I can open a "submenu" where I can choose data from the database, within the "Liquid"-section. Thats working pretty well.
Since the Topnav is rendered at the login of the page, the topnav wont be rendered again.
If I add a Liquid in the content section, I have to reload the data behind the "Liquid"-Dropdown.
That dropdown is encapsulated in a directive:
function liquidselect(Data){
return {
restrict: 'AE',
templateUrl: 'views/controls/liquid_select.html',
scope: {
selectedValues : '='
},
link: function(scope) {
},
controller: function ($scope) {
//
Data.post('ownrecipes').then(function (results) {
$scope.searchRes = results;
});
// debugger;
//$scope.searchRes = RecipeDataService.data;
$scope.disabled = undefined;
$scope.searchEnabled = false;
$scope.searchRes = [];
$scope.flavoring = {amount: {}};
$scope.updateModelValue = function (selected) {
// console.log(selected);
//
$scope.selectedValues = selected;
};
}
}
}
The communication to the server is done by a factory:
app.factory("Data", ['$http', 'toaster',
function ($http, toaster ) { // This service connects to our REST API
// var deffered = $q.defer();
var serviceBase = 'api/v1/';
var obj = {};
obj.toast = function (data) {
toaster.pop(data.status, "", data.message, 3000, 'trustedHtml');
}
obj.get = function (q) {
return $http.get(serviceBase + q).then(function (results) {
return results.data;
});
};
obj.post = function (q, object) {
return $http.post(serviceBase + q, object).then(function (results) {
return results.data;
});
};
obj.put = function (q, object) {
return $http.put(serviceBase + q, object).then(function (results) {
return results.data;
});
};
obj.delete = function (q) {
return $http.delete(serviceBase + q).then(function (results) {
return results.data;
});
};
return obj;
}]);
How can I update/reload the data of the directive from a child scope? Is there any chance?
Here is a plunker which showcases my problem and want I want to do:
http://plnkr.co/edit/bNANkQYZfBaS4CHH3dwX
I hope it's helpful for you :-)
Layout:
Basic Layout
i have created the custom service like this
app.service('userService', function($http,UrlService) {
return {
init: function(callback) {
$http.get(UrlService.baseUrl +'/api/users/list').then(function(user_response) {
callback(user_response);
});
}
}
})
Inside of my project main controller i have used like this to get the angular material design modal.
$scope.replyComplaint = function(user,complaint_id) {
complaint_id=user._id;
console.log(complaint_id)
$mdDialog.show({
controller: DialogCtrl,
templateUrl: 'submodules/user_management/replydialog.html',
resolve: { complaint_id : function() {return complaint_id;} },
locals: {
users: $scope.users
},
parent: angular.element(document.body),
clickOutsideToClose: true,
})
.then(function(response) {
$scope.response = response;
console.log(response);
}, function() {
//fail
});
};
created another controller for dialog as in the angular material docs as follows
function DialogCtrl($scope, $rootScope, $mdDialog, users,complaintService, UrlService, $http) {
complaintService.init(function(complaint_response) {
$scope.complaints = complaint_response.data;
$scope.getUsers();
});
$scope.getUsers = function(complaint_id) {
console.log(complaint_id);
$scope.hide = function() {
$mdDialog.hide();
};
$scope.cancel = function() {
$mdDialog.cancel();
};
$scope.replyMail = function(complaint_id) {
console.log(complaint_id);
$http.post(UrlService.baseUrl + '/api/complaints/complaint/'+complaint_id , {
complaint: "replyText"
}, $scope)
.then(function(response) {
console.log(name);
$state.reload();
}, function(response) {
console.log(name);
});
}
}
}
Now, i need to get the user_response data in DialogController. if i put console.log('$scope.users') inside of this userservice.init function, i can get the data. but not outside of it. how to get the response data outside of the userService.init function
userService.init(function(user_response) {
$scope.users = user_response.data;
}); //this is added in DialogController
Main intension is to get the user.comlaint_id in the post request of reply mail function . that user.complaint_id is a part of the user_response
Anyone please help me. Thanks
The $http.get call returns a promise, you can just use that.
app.service('userService', function($http,UrlService) {
return {
init: function(callback) {
return $http.get(UrlService.baseUrl +'/api/users/list');
}
}
});
Controller:
function Dialog($scope,$rootScope, $mdDialog,userService,UrlService,$http) {
// console.log(userService.init());
init();
function init() {
userService.init().then(function(response) {
$scope.users = response.data;
});
}
}
This also has the advantage of easier error handling:
function Dialog($scope,$rootScope, $mdDialog,userService,UrlService,$http) {
// console.log(userService.init());
init();
function init() {
userService.init().then(function(response) {
$scope.users = response.data;
}, function(error) {
// handle error
});
}
}
You should read up on angular/javascript promises and their chaining mechanism: angular promises
Here is the solution
userService.init(function(user_response) {
$scope.users = user_response.data;
$scope.init();
});
$scope.init = function() {
You can access $scope.users here
}
Call any method instead of init() in which you require $scope.users
I recently started to learn unit test for angular apps. And already faced up with problem. I can not take scope variable from inside executed function. Here is my factory code
angular.module('app').factory('AuthenticationService', AuthenticationService);
AuthenticationService.$inject = ['$http'];
function AuthenticationService($http) {
var service = {};
service.login = login;
return service;
function login(data, callback) {
$http({
method: 'POST',
url: CONFIG.getUrl('auth/login'),
data: data
}).then(function (response) {
callback(response);
}, function (error) {
callback(error);
});
}
Part of my controller file. I only yet wan to test login function
function AuthCtrl($scope, $location, AuthenticationService) {
var vm = this;
vm.login = login;
vm.dataLogin = {
user_id: '',
password: '',
};
function login() {
vm.dataLoading = true;
AuthenticationService.login(vm.dataLogin, function (response) {
if (response.status == 200) {
if (response.data.error_code == 'auth.credentials.invalid') {
vm.invalidCredentials = true;
} else {
vm.invalidCredentials = false;
if (response.data.session_state == 'otp_required') {
vm.userNumber = response.data.user_phone;
$localStorage['session_token'] = response.data.session_token;
vm.needForm = 'someForm';
} else {
AuthenticationService.setCredentials(response.data);
$state.go('dashboard');
}
vm.dataLoading = false;
}
}
});
}
}
});
And my spec.js
describe('AuthCtrl, ', function() {
var $scope, ctrl;
var authSrvMock;
var mockJson = {
user_id: '001',
session_token: 'some_token'
};
var mockLoginData = {
user_id: '0000102',
password: '123456'
};
var mockResponseData = {
data: {
"session_expires": 1453822506,
"session_state": "otp_required",
"session_token": "tokennnn",
"status": "success",
"user_id": "0000102",
"user_phone": "+7 (XXX) XXX-XX-89"
},
status: 200
};
beforeEach(function () {
authSrvMock = jasmine.createSpyObj('AuthenticationService', ['login', 'logout']);
module('app');
inject(function ($rootScope, $controller, $q) {
$scope = $rootScope.$new();
authSrvMock.login.and.returnValue(mockResponseData);
ctrl = $controller('AuthCtrl', {
$scope: $scope,
AuthenticationService: authSrvMock
});
});
});
it('should call login function and pass to dashboard', function () {
ctrl.login();
expect(authSrvMock.login).toHaveBeenCalled();
// until this everything works here just fine
});
});
But after I want to test vm.invalidCredentials, if I will write
expect(ctrl.invalidCredentials).toBe(false)
I will get the error
Expected undefined to be false.
Why I can't see variables?
Bit of a noob myself at Jasmine, but I'm guessing it's because you need to get the promise from your login() to return in Jasmine.
Look into using $q.defer(), or even $httpBackend.
After some more digging process and experiments I found solution.
Here what I did
(function () {
'use strict';
describe('AuthCtrl', function () {
var controller, scope, myService, q, deferred, ctrl;
var mockResponseData = {
response1: {
//...
},
response2: {
//...
},
response3: {
//...
}
};
beforeEach(module('app'));
beforeEach(inject(function ($controller, $rootScope, $q, $httpBackend, AuthenticationService) {
function mockHttp(data, callback) {
deferred = $q.defer();
deferred.promise.then(function (response) {
callback(response);
}, function (error) {
callback(error);
});
}
controller = $controller;
scope = $rootScope.$new();
myService = AuthenticationService;
q = $q;
myService.login = mockHttp;
}));
describe('when returning promises', function () {
beforeEach(function () {
ctrl = controller('AuthCtrl', {
$scope: scope,
myService: myService
});
ctrl.initController();
});
it('shows another form to validate login process', function () {
ctrl.login();
deferred.resolve(mockResponseData.response1);
scope.$digest();
expect(ctrl.invalidCredentials).toBe(false);
expect(ctrl.needForm).toEqual('2sAuth');
expect(ctrl.dataLoading).toBe(false);
});
});
});
})();
Since in my factory almost every method requires data and callback I've created mockHttp functions which takes those arguments and deferred promise. In it block I simply call need function, resolve promise with my prepared answers mock and check my expectations. Everything work. Thanks to for aiming in wich way to look
This is a follow-up question to Angular-ui modal, sending data into modal controller from $http
I have the following code where I want to get data via a factory to the modal.
$scope.docSetup = function() {
var modalInstance = $modal.open({
templateUrl : '/templates/dialog/docSetup.html',
controller : 'docSetupDlgCtrl',
resolve : {
dlgData : function(){
return TagService.list($scope.publication.id);
}
}
});
modalInstance.result.then(function (dlgData) {
$log.debug(dlgData);
}, function () {
$log.debug('Modal dismissed at: ' + new Date());
});
};
And here is the factory:
app.factory("TagService", function($http, $log){
return {
list: function(selectedDoc){
$log.info("Tag service at work => list");
var httpPromise = $http.post("tags/list", { publicationId: selectedDoc });
httpPromise.then(function (response) {
$log.log(response.data);
return response.data;
}, function (error) {
$log.error(error);
});
}
}
});
The above isn't resolving any data into dlgData. The factory is producing data and if I hardcode the data object into the 'resolve' function, it passes it.
return the entire httpPromise as well:
return httpPromise.then(function (response) {
$log.log(response.data);
return response.data;
}, function (error) {
$log.error(error);
});
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)
}
})