I am dealing with angular 1 component, I made a datatable component which accepts a dataset as a parameter.
here is how I am using datatable component.
index.html
...
<datatable dataset="ViewModel.dataset"></datatable>
...
index.controller.js
(function() {
'use strict';
angular
.module('DashboardApplication')
.controller('PagesIndexController', PagesIndexController);
function PagesIndexController() {
var self = this;
self.dataset = {};
Restangular.one('someurl').get().then(function( pages ) {
self.dataset = pages;
});
}
})();
datatable.component.js
(function() {
'use strict';
angular
.module('DashboardApplication')
.component('datatable', {
bindings: {
dataset: '<'
},
templateUrl: '/frontend/templates/dashboard/components/data-table.html',
controller: 'DataTableController'
});
})();
datatable.controller.js
(function() {
'use strict';
angular
.module('DashboardApplication')
.controller('DataTableController', DataTableController);
function DataTableController() {
var self = this;
console.log(self.dataset); // which is undefined!
}
})();
The problem is I'm getting undefined for dataset in datatable.controller.js. Is there any solution for this?!
Use the $onChanges life-cycle hook to see the value when it becomes defined:
angular
.module('DashboardApplication')
.controller('DataTableController', DataTableController);
function DataTableController() {
var self = this;
//console.log(self.dataset); // which is undefined!
this.$onChanges = function(changesObj) {
if (changesObj.dataset) {
console.log(changesObj.dataset.currentValue);
};
});
}
For more information, see AngularJS Developer Guide -- Components.
I think you are missing a
controllerAs: 'vm'
line from your component which will bind the model to "this" in the your controller instead of $scope (also means you can reach your viewmodel as "vm" inside your view, like:
ng-if="vm.dataset"
But I think it will still be undefined in that exact moment, you have several options here:
you can pass the promise to the component and write a then on it
you can place an ng-if="dataset && dataset.length" where you call the component in your outer html. This way the logic inside your component will only trigger when there is actually data in the property.
<datatable ng-if="ViewModel.dataset" dataset="ViewModel.dataset"></datatable>
you can also write something like this in your component:
$scope.$watch('self.dataset', function () {
if (self.dataset) {
alert ('hi');
}
});
Related
Learning AngularJS is a Work In Progress for me so I just want to understand why/when we should use one over another in particular case below. Is it just matter of taste or more than that? See examples below.
In both cases, when user clicks OK button, create() function of parent controller is called from child controller.
RESOLVE STYLE
CreateController
...
var vm = this;
vm.create = create;
function create() {
console.log('Created!');
}
vm.openCreateModal = function() {
vm.modalInstance = $uibModal.open({
...
resolve: {
create: function() {
return create;
},
// Others if any
}
});
}
...
CreateModalController
...
vm.ok = function() {
create();
$uibModalInstance.close('ok');
};
...
SCOPE STYLE
CreateController
...
var vm = this;
vm.create = create;
function create() {
console.log('Created!');
}
vm.openCreateModal = function() {
vm.modalInstance = $uibModal.open({
...
scope: $scope,
resolve: {
}
});
}
...
CreateModalController
...
vm.ok = function() {
$scope.$parent.vm.create();
$uibModalInstance.close('ok');
};
...
Update
The actual reason why I ask this question is, accessining/injecting parent/root/container like objects of one service/controller in/to another controller/service is considered as a "bad practise" in some languages/frameworks I use.
The idea of resolve is that it will run that first before initializing the rest of your code. Generally you would use resolve in your routing like so:
$routeProvider
.when('/', {
templateUrl: "views/view.html",
caseInsensitiveMatch: true,
resolve: {
load: function() {
localStorage['Location'] = "/View";
}
}
})
In the above example resolve will fire the load function before my controller is ever initialized. On the other hand scope is used to bind directly to something in a controller or directive. You should use scope when triggering functions and binding to values between controllers and directives.
To add to this based on the comments below, if the resolve fails it will reject the modal and the window will not open.
I'm trying to figure out AngularJS directives. I've got the following JSFiddle with an example of something I'm trying to do. https://jsfiddle.net/7smor9o4/
As you can see in the example, I expect the vm.alsoId variable to be equal to vm.theId. In the template vm.theId displays the correct value but vm.alsoId does not.
What am I doing wrong? How could I accomplish my goal.
If it helps the final idea is to execute something as the following:
function directive(service) {
var vm = this;
vm.entity = null;
init();
function init() {
service.getEntity(vm.theId).then(function (entity) {
vm.entity = entity;
});
}
}
As you've noticed, the bindToController bindings are not immediately available in the controller's constructor function (unlike $scope, which are). What you're looking for is a feature introduced with Angular 1.5: Lifecycle Hooks, and specifically $onInit.
You had the right idea; simply replace your init function definition and invocation as follows:
vm.$onInit = function () {
service.getEntity(vm.theId).then(function (entity) {
vm.entity = entity;
});
};
And here is your updated fiddle.
(Alternatively, without this solution, you'd have needed a watch.)
Angular recommends that you bind a controller "only when you want to expose an API to other directives. Otherwise use link."
Here's a working fiddle using the link function.
angular.module('app', [])
.directive('directive', directive);
angular.element(function() {
angular.bootstrap(document, ['app']);
});
function directive() {
return {
restrict: 'E',
scope: {
theId: '<'
},
template: `
alsoId: <span ng-bind="alsoId"></span>
theId: <span ng-bind="theId"></span>`,
link: link
};
}
function link(scope, element, attrs) {
init();
function init() {
scope.alsoId = scope.theId;
}
}
When i create a component with a function which is returning a component object, my component is not initialize! I am sharing these two situation. Can someone explain me what is the difference between them?
Html:
<div ng-app="demoApp">
<navbar></navbar>
</div>
Working code: Fiddle
var NavbarTemplate = `<button ng-click="$ctrl.clickTest()">Click Test</button>`;
var navbar = {
controller: function() {
this.clickTest = clickTest;
function clickTest() {
alert("hello");
}
},
template: NavbarTemplate
};
angular.module('demoApp', []).component('navbar', navbar);
Faulty (without error) code: Fiddle
function getComponent(){
var template = `<button ng-click="$ctrl.clickTest()">Click Test</button>`;
var component = {
controller: function() {
this.clickTest = clickTest;
function clickTest() {
alert("hello");
}
},
template: template
}
return component;
}
angular.module('demoApp', []).component('navbar', getComponent);
You need to add parentheses to getComponent passed as a parameter to the last line like so:
angular.module('demoApp', []).component('navbar', getComponent());
Using simply getComponent (without parentheses) passes a reference to the getComponent function to component() without executing it. However, angular is expecting an object containing your component configuration.
Thus, passing getComponent() calls the function and returns the component configuration object passing said configuration object to angular component() initializer rather than a reference to the function getComponent.
Inside the controller I am trying to breakup my code into named functions for readability. However, in the parameterized named functions the scope and the injected dependency are all null. How do access these inside the named functions. Thanks for you help.
(
function() {
'use strict';
var moduleName = 'ufsrAppModule';
var controllerName = 'ufsrController';
var dependencyInjection = ['api', 'appHost', 'userAccount', 'userProfileFactory', 'fsrFactory', 'userFsrFactory', internalFunc];
angular.module(moduleName)
.controller(controllerName, dependencyInjection);
function internalFunc(api, appHost, userAccount, userProfileFactory, fsrFactory, userFsrFactory) {
var vm = this; //controller AS in ng-controller, do not use $scope
init(api, appHost, userAccount, userProfileFactory, fsrFactory, userFsrFactory, vm);
}
function init(api, appHost, userAccount, userProfileFactory, fsrFactory, userFsrFactory, vm) {
vm.facilityChanged = facilityChanged;
...
...
function facilityChanged(vm, fsrFactory) {
/*update UI then retrieve services*/
vm.postStatus = undefined;
vm.services = undefined;
vm.roles = undefined;
vm.services = fsrFactory.service().query({
/*parameters*/
FacilityID: vm.facility
})
.$promise.then(
function(data) {
vm.services = data;
});
}
}
})();
Strict DI can be done separately in this style
angular.module(moduleName)
.controller(controllerName, controllerFunction);
controllerFunction.$inject = ['$scope', '$http'];
function controllerFunction($scope, $http) {
...
}
This style is also recommended by John Papa's Angular style guide.
The facilityChanged is not working because its parameters are overwriting those that are passed into init
It can be fixed by changing
function facilityChanged(vm, fsrFactory) {
to
function facilityChanged() {
Edit: Attached jsbin
I strongly recommend putting the init function inside your controller function to save the parameter passing, just like the activate function in John Papa's guide.
Refined jsbin
How would you on connect two way data binding between a service and controller? I've looked at a couple posts, such as How to make two-way data binding between service and controller, but I don't understand the answer. Can someone provide a simple, high-level explanation or example? I've looked into using $watch, but I've also noticed a lot of people saying it shouldn't be used in controllers ie. Angular JS - you probably shouldn't use $watch in your controllers, which just adds to the confusion since I don't know where else I would add it.
Included dependencies from main app.js
(function() {
'use strict';
angular.module('myApp', [
'ngRoute',
'app.controller',
'app.service',
'component.navbar'
]);
})();
Included dependencies from navbar component
(function() {
'use strict';
/* Navbar Component */
angular.module('component.navbar', [
'component.navbar.controller',
'component.navbar.directive',
'component.navbar.service'
])
.run(function( NavbarService ) {
NavbarService.getJSON();
});
})();
Navbar component snippets
(function() {
'use strict';
angular.module('component.navbar.service', [])
.factory('NavbarService', ['$http', function( $http ) {
var navbarJSON = [];
var active = 0;
var getJSON = function() {
return $http.get('app/data/navlinks.json')
.success( function( data ) {
navbarJSON = data;
});
}
var getData = function( ) {
return navbarJSON;
}
var setActive = function( index ) {
active = index;
}
var getActive = function() {
return active;
}
return {
getJSON: getJSON,
getData: getData,
setActive: setActive,
getActive: getActive
}
}]);
})();
(function() {
'use strict';
angular.module('component.navbar.controller', [])
.controller('NavbarController', ['$scope', 'NavbarService', function( $scope, NavbarService ) {
$scope.navbarData = NavbarService.getJSON();
$scope.active = NavbarService.getActive();
$scope.setActive = function( index ) {
//$scope.active = $scope.navbarData[index]
NavbarService.setActive( index );
}
}]);
})();
You could have just chosen to pass the service as a $scope property and then use its methods as it is:
Javascript
Controller
.controller('NavbarController', ['$scope', 'NavbarService', function( $scope, NavbarService ) {
NavbarService.getJSON();
$scope.navbar = NavbarService;
}]);
HTML (Example)
<nav ng-controller="NavbarController">
<a ng-repeat="link in navbar.getData()"
ng-class="{'active': navbar.getActive() == $index}"
ng-click="navbar.setActive($index)">
{{link.label}}
</a>
</nav>
You need to import your service module to your controller module first
angular.module('component.navbar.controller', ['component.navbar.service'])
and I can't see where you have called NavBarService.getJSON (), so called it first to get the data.
Why not just expose active in your service? If you need getter/setter, use implicit ones.
Regarding the suggestion not to use $watch in controllers. That is a pretty generic statement. If $watch is the right tool, use it. If you can achieve the same with an event listener, do that.
In your case your service could broadcast an event on the root scope to signal a change of state. You could listen for that event in your controller and update your value instead of using $watch.