Load service data into angular component - javascript

I have a component which takes care of drawing two lists, but in the component there is no data so nothing is drawn.
myController
function loadAllData() {
Admin.getAllSettings()
.then(function (settings) {
$scope.settings = settings.data;
})
}
myComponent
{
bindings: {
selectedData: '=',
availableData: '<'
},
templateUrl: 'global/twoListSelector.directive.html',
controller: function () {
var me = this;
console.log(me);
}
}
myView
<two-side-selector selectedData="doctorProperties" availableData="settings"></two-side-selector>
In the console.log the output for me.settings is undefined. Shouldn't the digest cycle update the setting property so it gets to the component? The service is returning data correctly but it is not getting to the component
I am using angular 1.5.9

Try to use selected-data and available-data attributes at markup:
<two-side-selector selected-data="doctorProperties" available-data="settings"></two-side-selector>
AngularJS convert dash-separated attributes to camel-case by itself

Related

Two way binding angular component with parent controller

I have an angular component (vendor-filters) that I would like to pass data to and from a parent controller. The purpose is to create a filter off of mainInfo, and pass that data back to the parent controller where it will reflect the filtering in the component. My problem is that this mainInfo variable is returning undefined in the component controller. Here's my code :
html
<div ng-controller="kanban as ctrl">
<vendor-filters mainInfo="ctrl.mainInfo"></vendor-filters>
<div class="qt-kb">
<kanban-list label="Incoming" type="incoming" cards="ctrl.mainInfo.incoming"></kanban-list>
<kanban-list label="In Progress" type="progress" cards="ctrl.mainInfo.inProgress"></kanban-list>
<kanban-list label="Waiting For Documentation" type="docs" cards="ctrl.mainInfo.documentation"></kanban-list>
<kanban-list label="Pending Approval" type="closed" cards="ctrl.mainInfo.closed"></kanban-list>
</div>
Parent Controller :
app.controller("kanban", ["$scope", "assignmentDataService", "globalSpinner", function ($scope, assignmentDataService, globalSpinner) {
const vm = this;
vm.mainInfo = [];
activate();
function activate() {
getData();
}
function getData() {
var promise = assignmentDataService.getData()
.then(function(data) {
vm.mainInfo = data;
});
globalSpinner.register(promise);
};
}]);
Component controller:
class VendorFilterCtrl {
constructor($http, $scope, $timeout,assignmentDataService) {
this.$scope = $scope
this.$http = $http;
const vm = this;
//I could be initializing this wrong but this is where I'm trying to get
//the data.
vm.data = vm.mainInfo;
}
app.controller('kanban').component("vendorFilters", {
templateUrl: "app/components/vendorFilters.html",
bindings: {
store: "=?store",
onChange: '&',
mainInfo: '<'
},
controller: VendorFilterCtrl,
controllerAs: "ctrl",
bindToController: true
});
Basically I'm trying to get the mainInfo from the parent controller into the component and visa versa. Any idea why this isn't working?
Start by using kebab-case for the attribute:
<vendor-filters ̶m̶a̶i̶n̶I̶n̶f̶o̶ main-info="ctrl.mainInfo"></vendor-filters>
NEXT
Fix this:
app ̶.̶c̶o̶n̶t̶r̶o̶l̶l̶e̶r̶(̶'̶k̶a̶n̶b̶a̶n̶'̶)̶ .component("vendorFilters", {

AngularJS ES6 parent function not being called by child component

I'm a bit new to the ES6 syntax/structure of Angular 1.x, and I'm running into an issue with passing a function from a parent controller to a child controller.
This is how the app is tied together (I use webpack + babel with this as the entry-point):
const requires = [
'ngRoute',
];
//App
angular.module('kanban',requires)
.controller('dashboardCtrl', dashboardCtrl)
.component('board', board)
.component('task', task)
.config(routes);
In my routes, I have a single route, which is my 'parent'
export default function($routeProvider) {
$routeProvider
.when('/', {
template: dashboardTemplate,
controller: 'dashboardCtrl',
controllerAs: '$ctrl',
});
}
Who's controller looks like this:
export default function($rootScope) {
$rootScope.title = 'Kanban';
let _this = this;
this.boards = [
{
_id: 'b1',
title: 'backlog',
tasks: ['t1', 't2'],
}
];
this.deleteBoard = function(board) {
console.log(board);
let index = _this.boards.indexOf(board);
if (index !== -1) {
_this.boards.splice(index, 1);
}
};
And in the template, the child is created with ng-repeat, passing in the function
<board ng-repeat="board in $ctrl.boards" board="board" onDelete="$ctrl.deleteBoard(board)" ></board>
And the board binds the attribute as a function with an &
export const board = {
template: boardTemplate,
controller: boardCtrl,
bindings: {
board: '=',
onDelete: '&',
}
};
And the function is added to the controller within a different function:
export default function boardCtrl() {
let _this = this;
this.deleteBoard = function(){
console.log(_this.onDelete);
_this.onDelete({board: _this.board});
};
}
And called with a click:
<button ng-click="$ctrl.deleteBoard()"></button>
I can reach the board (child) controller's function, which prints this in the console:
function (locals) {
return parentGet(scope, locals);
}
And returns no errors, but the console.log in the parent deleteBoard function does not get called.
What is happening here? Why does the child seem to recognize that it is calling something in the parent scope, but is not reaching it?
Turns out this issue was because of how the attribute was named in the parent template, where
<board onDelete="$ctrl.deleteBoard(board)"></board>
needed to be the following instead:
<board on-delete="$ctrl.deleteBoard(board)"></board>
even though the attribute is bound as "onDelete" on the child controller.

How to pass dynamically generated data to a different state in Angular UI-Router

Question: how do I access a dynamically generated data in scope B, when I go from scope A and generate this data in scope A, using angular's ui-controller. Data is not available when the scope is initialized.
Note: I am fine with showing request data in the URL. I'm looking for the simplest way for new state to read data it needs and pass it to server and properly generate its contents.
When the page loads, it fetches data from server and populates scope "tests" with new data. This new data is shown on the page. I create links to scope "test" with this data. Links look like this:
<a ui-sref="test({id:test._id})">{{test.name}}</a>
On a rendered page it looks like this:
<a ui-sref="test({id:test._id})" class="ng-binding" href="#/test/57adc0e30a2ced3810983640">A test</a>
The href is correct and points to a database reference of an item. My goal is to have this reference as a variable in scope "test". My state provider:
$stateProvider
.state('tests', {
url: '/tests/',
templateUrl: 'test/index.html',
controller: 'Test.IndexController',
controllerAs: 'vm',
data: { activeTab: 'tests' }
})
.state('test', {
url: '/test/{id}',
templateUrl: 'test/item.html',
controller: 'Test.ItemController',
controllerAs: 'vm',
data: {
activeTab: 'tests',
testId: '{id}'
}
});
So far no matter what I tried I couldn't access "testId" in the "test" scope. It was either "undefined", created errors or returned "itemId: {id}".
My Item.Controller:
(function () {
'use strict';
function Controller(TestService) {
var vm = this;
vm.test = null;
function getTest(id) {
TestService.GetTestById(id).then(function(test) {
vm.test = test;
});
}
function initController() {
getTest(...);
}
initController();
}
angular
.module('app')
.controller('Test.ItemController', Controller);
})();
TestService provides http get methods for getting data from server.
(function () {
'use strict';
function Service($http, $q) {
var service = {};
function handleSuccess(res) {
return res.data;
}
function handleError(res) {
return $q.reject(res.data);
}
function GetTestById(_id) {
var config = {
params: {
testId: _id
}
};
return $http.get('/api/tests/:testId', config).then(handleSuccess, handleError);
}
service.GetTests = GetTests;
service.GetTestById = GetTestById;
return service;
}
angular
.module('app')
.factory('TestService', Service);
})();
I tried $scope - scope is not defined. I tried a number of other techniques, shown by other users with similar success - either "undefined" or error of some sort.
This is based on another person's code so there may be obvious mistakes, please let me know if you find any. If you need more code, let me know - I'll upload it to github (its a messy work in progress at the moment so I'm not sure what should be uploaded).

Passing data through open modal function Angular uibModal

I'm trying to figure out how to pass unit_number into the modal when it pops up. I'm pretty new with Angular and I'm a little confused with what resolve: and group: are doing and how I can include the unit_number in that return statement.
$scope.openTenantModal = function (unit_number) {
var modalInstance = $uibModal.open({
animation: true,
templateUrl: 'views/addtenantmodal.html',
controller: 'AddTenantModalCtrl',
size: 'large',
resolve: {
group: function () {
return $scope.group;
}
}
});
modalInstance.result.then(function () {
}, function () {
});
};
You are using ui-bootstrap
Bootstrap components written in pure AngularJS
To pass a variable to a modal's controller you need to use
resolve: {
A: function() {
return 'myVal'
}
}
And then you can access that variable 'A' from the modal`s controller by injecting it
controller: ['A', function(A) {
// now we can add the value to the scope and use it as we please...
$scope.myVal = A;
}]
Check out: https://angular-ui.github.io/bootstrap/#/modal
Resolve:
Members that will be resolved and passed to the controller as locals; it is equivalent of the resolve property in the router.
And group is just a member (it could be anything you choose)
Just add a property in resolve object unitNumber with a function returning unit_number value from it. So that you can get the unit_number value inside AddTenantModalCtrl by injecting unitNumber dependency in controller factory function.
resolve: {
group: function () {
return $scope.group;
},
unitNumber: function(){
return unit_number
}
}
Note: Don't directly do unitNumber: unit_number, because when you have that, angular DI system will try to search the dependency
with name unit_number(value) and It will try to evaluate it as
function. Resultant you will get $injector error in console.

Angular ui-router resolve value as string

With ui-router, I add all resolve logic in state function like this;
//my-ctrl.js
var MyCtrl = function($scope, customers) {
$scope.customers = customers;
}
//routing.js
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: { // <-- I feel this must define as like controller
customers: function(Customer, $stateParams) {
return Customer.get($stateParams.id);
}
}
});
However IMO, resolve object must belong to a controller, and it's easy to read and maintain if it is defined within a controller file.
//my-ctrl.js
var MyCtrl = function($scope, customers) {
$scope.customers = customers;
}
MyCtrl.resolve = {
customers: function(Customer, $stateParams) {
return Customer.get($stateParams.id);
};
};
//routing.js
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: 'MyCtrl.resolve' //<--- Error: 'invocables' must be an object.
});
However, When I define it as MyCtrl.resolve, because of IIFE, I get the following error.
Failed to instantiate module due to: ReferenceError: MyCtrl is not defined
When I define that one as string 'MyCtrl.resolve', I get this
Error: 'invocables' must be an object.
I see that controller is defined as string, so I think it's also possible to provide the value as string by using a decorator or something.
Has anyone done this approach? So that I can keep my routings.js clean and putting relevant info. in a relevant file?
It sounds like a neat way to build the resolve, but I just don't think you can do it.
Aside from the fact that "resolve" requires an object, it is defined in a phase where all you have available are providers. At this time, the controller doesn't even exist yet.
Even worse, though, the "resolve" is meant to define inputs to the controller, itself. To define the resolve in the controller, then expect it to be evaluated before the controller is created is a circular dependency.
In the past, I have defined resolve functions outside of the $stateProvider definition, at least allowing them to be reused. I never tried to get any fancier than that.
var customerResolve = ['Customer', '$stateParams',
function(Customer, $stateParams) {
return Customer.get($stateParams.id);
}
];
// ....
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: {
customers: customerResolve
}
});
This question is about features of ui-router package. By default ui-router doesn't support strings for resolve parameter. But if you look at the source code of ui-router you will see, that it's possible to implement this functionality without making direct changes to it's code.
Now, I will show the logic behind suggested method and it's implementation
Analyzing the code
First let's take a look at $state.transitionTo function angular-ui-router/src/urlRouter.js. Inside that function we will see this code
for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
locals = toLocals[l] = inherit(locals);
resolved = resolveState(state, toParams, state === to, resolved, locals, options);
}
Obviously this is where "resolve" parameters are resolved for every parent state. Next, let's take a look at resolveState function at the same file. We will find this line there:
dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
var promises = [dst.resolve.then(function (globals) {
dst.globals = globals;
})];
This is specifically where promises for resolve parameters are retrieved. What's good for use, the function that does this is taken out to a separate service. This means we can hook and alter it's behavior with decorator.
For reference the implementation of $resolve is in angular-ui-router/src/resolve.js file
Implementing the hook
The signature for resolve function of $resolve is
this.resolve = function (invocables, locals, parent, self) {
Where "invocables" is the object from our declaration of state. So we need to check if "invocables" is string. And if it is we will get a controller function by string and invoke function after "." character
//1.1 Main hook for $resolve
$provide.decorator('$resolve', ['$delegate', '$window', function ($delegate, $window){
var service = $delegate;
var oldResolve = service.resolve;
service.resolve = function(invocables, locals, parent, self){
if (typeof(invocables) == 'string') {
var resolveStrs = invocables.split('.');
var controllerName = resolveStrs[0];
var methodName = resolveStrs[1];
//By default the $controller service saves controller functions on window objec
var controllerFunc = $window[controllerName];
var controllerResolveObj = controllerFunc[methodName]();
return oldResolve.apply(this, [controllerResolveObj, locals, parent, self]);
} else {
return oldResolve.apply(this, [invocables, locals, parent, self]);
}
};
return $delegate;
}]);
EDIT:
You can also override $controllerProvider with provider like this:
app.provider("$controller", function () {
}
This way it becomes possible to add a new function getConstructor, that will return controller constructor by name. And so you will avoid using $window object in the hook:
$provide.decorator('$resolve', ['$delegate', function ($delegate){
var service = $delegate;
var oldResolve = service.resolve;
service.resolve = function(invocables, locals, parent, self){
if (typeof(invocables) == 'string') {
var resolveStrs = invocables.split('.');
var controllerName = resolveStrs[0];
var methodName = resolveStrs[1];
var controllerFunc = $controllerProvider.getConstructor(controllerName);
var controllerResolveObj = controllerFunc[methodName]();
return oldResolve.apply(this, [controllerResolveObj, locals, parent, self]);
} else {
return oldResolve.apply(this, [invocables, locals, parent, self]);
}
};
Full code demonstrating this method http://plnkr.co/edit/f3dCSLn14pkul7BzrMvH?p=preview
You need to make sure the controller is within the same closure as the state config. This doesn't mean they need to be defined in the same file.
So instead of a string, use a the static property of the controller:
resolve: MyCtrl.resolve,
Update
Then for your Controller file:
var MyCtrl;
(function(MyCtrl, yourModule) {
MyCtrl = function() { // your contructor function}
MyCtrl.resolve = { // your resolve object }
yourModule.controller('MyCtrl', MyCtrl);
})(MyCtrl, yourModule)
And then when you define your states in another file, that is included or concatenated or required after the controller file:
(function(MyCtrl, yourModule) {
configStates.$inject = ['$stateProvider'];
function configStates($stateProvider) {
// state config has access to MyCtrl.resolve
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: MyCtrl.resolve
});
}
yourModule.config(configStates);
})(MyCtrl, yourModule);
For production code you will still want to wrap all these IIFEs within another IIFEs. Gulp or Grunt can do this for you.
If the intention is to have the resolver in the same file as the controller, the simplest way to do so is to declare the resolver at the controller file as a function:
//my-ctrl.js
var MyCtrl = function($scope, customers) {
$scope.customers = customers;
}
var resolverMyCtrl_customers = (['Customer','$stateParams', function(Customer, $stateParams) {
return Customer.get($stateParams.id);
}]);
//routing.js
$stateProvider.state('customers.show', {
url: '/customers/:id',
template: template,
controller: 'MyCtrl',
resolve: resolverMyCtrl_customers
});
This should work.
//my-ctrl.js
var MyCtrl = function($scope, customer) {
$scope.customer = customer;
};
//routing.js
$stateProvider
.state('customers.show', {
url: '/customers/:id',
template: template,
resolve: {
customer: function(CustomerService, $stateParams){
return CustomerService.get($stateParams.id)
}
},
controller: 'MyCtrl'
});
//service.js
function CustomerService() {
var _customers = {};
this.get = function (id) {
return _customers[id];
};
}

Categories