Access object passed to a component inside it's own controller - javascript

So i got this component. and i can access the data that is passed to its bindings.
But only in it'template. I need to access the object in the component's own controller to do some stuff with it. And i am a bit stuck figuring it out.
Here is the component:
angular.module('MpWatchModule').component('mPointlite', {
bindToController: false,
restrict: 'E',
templateUrl: '/NG-MPWatch/Templates/mPointLite.html',
controller: function (NgMap) {
this.googleMapsUrl = 'https://maps.google.com/maps/api/js?key=<my_api_key>';
NgMap.getMap().then(function (map) {
this.map = map;
this.map.setMapTypeId('terrain');
// this.map.setMapTypeId('satellite');
this.mpObjs = mpdata;
});
},
controllerAs: 'mpl',
bindings: {
mpdata: '<',
},
});
And here is the markup in the components template:
<div map-lazy-load="https://maps.google.com/maps/api/js" map-lazy-load-params="{{mpl.googleMapsUrl}}">
<ng-map center="Hungary"
zoom="8"
class="gmap"
disable-default-u-i="true"
draggable-cursor="auto"
max-zoom="15"
min-zoom="8"
liteMode="true"
tilt="0">
<div ng-repeat="m in mpl.mpObjs">
<marker position="{{m.position}}">
</marker>
</div>
</ng-map>
</div>
Here is the markup from the page:
<m-pointlite mpdata="mpdl.mpObjs">
</m-pointlite>
And what i will need is to access the objects coming from the mpdl.mpObjs on the page. And do some stuff with them in the components controller, than display it in the components template. I accomplished most of it, just this missing link in the chain.
I appreciate anyone's help, and advise in advance.
Thanks

Remove bindToController: false
By default an angular component has bindToController set to true and allows you to access the bindings within the scope of the controller.
Then in your controller adjust the line:
this.mpObjs = mpdata;
to be this.mpObjs = this.mpdata;
I would suggest laying the code out like so just for better readability and easier to make changes/work with and follows the angular style guide:
(function () {
'use strict';
angular
.module('MpWatchModule')
.component('mPointlite', {
restrict: 'E',
bindings: {
mpdata: '<',
},
templateUrl: '/NG-MPWatch/Templates/mPointLite.html',
controller: PointLiteController,
controllerAs: 'mpl'
});
PointLiteController.$inject = ['NgMap'];
function PointLiteController(NgMap) {
var mpl = this;
mpl.googleMapsUrl = 'https://maps.google.com/maps/api/js?key=<my_api_key>';
activate();
function activate() {
NgMap.getMap().then(function (map) {
mpl.map = map;
mpl.map.setMapTypeId('terrain');
mpl.mpObjs = mpl.mpdata;
});
}
}
})();

I'm just putting the JS code alone. Try like below you will get the bindings inside controller
JS:
controller: function (NgMap) {
var ctrl = this;
this.googleMapsUrl = 'https://maps.google.com/maps/api/js?key=<my_api_key>';
NgMap.getMap().then(function (map) {
this.map = map;
this.map.setMapTypeId('terrain');
// this.map.setMapTypeId('satellite');
this.mpObjs = ctrl.mpdata;
});
}

Related

Binding a Directive Controller's method to its parent $scope

I will explain what exactly I'm trying to do before explaining the issue. I have a Directive which holds a form, and I need to access that form from the parent element (where the Directive is used) when clicking on a submit button to check fi the form is valid.
To do this, I am trying to use $scope.$parent[$attrs.directiveName] = this; and then binding some methods to the the Directive such as this.isValid which will be exposed and executable in the parent.
This works fine when running locally, but when minifying and building my code (Yeoman angular-fullstack) I will get an error for aProvider being unknown which I traced back to a $scopeProvider error in the Controller.
I've had similar issues in the past, and my first thought was that I need to specifically say $inject for $scope so that the name isn't lost. But alas.....no luck.
Is something glaringly obvious that I am doing wrong?
Any help appreciated.
(function() {
'use strict';
angular
.module('myApp')
.directive('formDirective', formDirective);
function formDirective() {
var directive = {
templateUrl: 'path/to/template.html',
restrict: 'EA',
scope: {
user: '='
},
controller: controller
};
return directive;
controller.$inject = ['$scope', '$attrs', 'myService'];
function controller($scope, $attrs, myService) {
$scope.myService = myService;
// Exposes the Directive Controller on the parent Scope with name Directive's name
$scope.$parent[$attrs.directiveName] = this;
this.isValid = function() {
return $scope.myForm.$valid;
};
this.setDirty = function() {
Object.keys($scope.myForm).forEach(function(key) {
if (!key.match(/\$/)) {
$scope.myForm[key].$setDirty();
$scope.myForm[key].$setTouched();
}
});
$scope.myForm.$setDirty();
};
}
}
})();
Change the directive to a component and implement a clear interface.
Parent Container (parent.html):
<form-component some-input="importantInfo" on-update="someFunction(data)">
</form-component>
Parent controller (parent.js):
//...
$scope.importantInfo = {data: 'data...'};
$scope.someFunction = function (data) {
//do stuff with the data
}
//..
form-component.js:
angular.module('app')
.component('formComponent', {
template:'<template-etc>',
controller: Controller,
controllerAs: 'ctrl',
bindings: {
onUpdate: '&',
someInput: '<'
}
});
function Controller() {
var ctrl = this;
ctrl.someFormThing = function (value) {
ctrl.onUpdate({data: value})
}
}
So if an event in your form triggers the function ctrl.someFormThing(data). This can be passed up to the parent by calling ctrl.onUpdate().

Angular directive not getting info from controller

I'm trying to write a directive to set the active classes to my header, which is shared all over my website, but for some reason it doesn't get the info from the controller.
Here's my directive
(function () {
'use strict';
angular
.module('app.actions')
.directive('header', header);
function header() {
var directive = {
restrict: 'A',
controller: function ($scope) {
var vm = this;
vm.headerActive = 'none';
vm.setHeaderActive = setHeaderActive;
function setHeaderActive(item) {
vm.headerActive = item;
console.log(item)
}
},
controllerAs: 'vm'
};
return directive;
}
})();
I'm setting it as an attribute correctly and I don't get any error messages.
Any help will be appreciated, thanks

Angular component won't display

I have piece of html I want to show as a component, as I'm not manipulating the DOM.
As a directive it works fine, but as a component it doesn't. I have made components before with no problem, just can't see what the issue is here.
If I comment in the component code, and the directive out, it doesn't work.
Any idea what I've done wrong?
(function() {
"use strict";
angular
.module('x.y.z')
// .component('triangularStatus', {
// bindings: {
// value: '=',
// dimension: '=?'
// },
// templateUrl: '/path/to/triangular-status.html',
// controller: TriangularStatusController,
// controllerAs: 'vm'
// });
.directive('triangularStatus', triangularStatus);
function triangularStatus() {
var directive = {
scope: {
value: '=',
dimension: '=?'
},
replace: true,
templateUrl: '/path/to/triangular-status.html',
controller: TriangularStatusController,
controllerAs: 'vm',
};
return directive;
}
TriangularStatusController.$inject = [];
function TriangularStatusController() {
var vm = this;
}
})();
Here is the working code, most probably you are not using vm.values to access data.
Just be sure you are using right version of angular js ~1.5
(function(angular) {
angular.module('x.y.z', [])
.component('triangularStatus', {
bindings: {
value: '=',
dimensions:'=?'
},
template: '{{vm.value}} <br/> {{vm.dimensions}}' ,
controller: TriangularStatusController,
controllerAs: 'vm'
});
TriangularStatusController.$inject = [];
function TriangularStatusController() {
}
})(window.angular);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.js"></script>
<div ng-app = "x.y.z">
<triangular-status value="24" dimensions="348"></triangular-status>
</div>
The definition of your component, using bindings, is not directly equivalent to the definition of your directive, using scope, even though both are defined to use controllerAs. This is because your component will be binding directly to the controller, and your directive will be binding to $scope (by default).
I've used your code in the snippet below, slightly modified to allow the component and directive(s) to be used together. I've also added an additional directive that makes use of bindToController:true to demonstrate a directive that behaves a little more like a component in binding its attribute values directly to the controller, rather than to $scope.
I've also used a very basic shared template that attempts to show the bound attribute values by looking for them on $scope, followed by looking for them on vm (the ControllerAs).
(function() {
"use strict";
var templateBody = '<h2>$scope</h2>' +
'<p>value: {{value}}</p><p>dimension: {{dimension}}</p>' +
'<h2>vm</h2>' +
'<p>vm.value: {{vm.value}}</p><p>vm.dimension: {{vm.dimension}}</p>';
angular
.module('x.y.z', [])
.component('triangularStatusComponent', {
bindings: {
value: '=',
dimension: '=?'
},
template: '<div><h1>Triangular Status Component</h1>' + templateBody + '</div>',
controller: TriangularStatusController,
controllerAs: 'vm'
})
.directive('triangularStatusDirective', triangularStatusDirective)
.directive('triangularStatusDirectiveBound', triangularStatusDirectiveBound);
function triangularStatusDirective() {
var directive = {
scope: {
value: '=',
dimension: '=?'
},
replace: true,
template: '<div><h1>Triangular Status Directive</h1>' + templateBody + '</div>',
controller: TriangularStatusController,
controllerAs: 'vm',
};
return directive;
}
function triangularStatusDirectiveBound() {
//https://docs.angularjs.org/api/ng/service/$compile#-bindtocontroller-
var directive = {
scope: {
value: '=',
dimension: '=?'
},
bindToController: true,
replace: true,
template: '<div><h1>Triangular Status Directive Bound</h1>' + templateBody + '</div>',
controller: TriangularStatusController,
controllerAs: 'vm',
};
return directive;
}
TriangularStatusController.$inject = [];
function TriangularStatusController() {
var vm = this;
}
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="x.y.z">
<triangular-status-component value="'componentValue'" dimension="'componentDimension'">
</triangular-status-component>
<hr>
<triangular-status-directive value="'directiveValue'" dimension="'directiveDimension'">
</triangular-status-directive>
<hr>
<triangular-status-directive-bound value="'directiveValueBound'" dimension="'directiveDimensionBound'">
</triangular-status-directive-bound>
</div>
If you're finding that your code works as a directive, where your values are bound to $scope, but not as a component, where your values are bound to the controller, I would assume either your template html (most likely?) or your controller function are relying on trying to access your values as though they were on $scope. To confirm this, you may notice there are errors being logged to your javascript console that will help you zero in.
I think the only problem is, that your missing the brackets:
angular.module('x.y.z')
change to
angular.module('x.y.z', [])
https://docs.angularjs.org/api/ng/function/angular.module
As was mentioned in the comment, I need to clarify, the problem can be how are your JS files ordered or bundled, some other JS file executed later can overwrite this module and therefor you will not see any tag rendered.

Angular 1.5.x - Issue with nested components

First of all, I'm using components.
I have this "parent" component:
(function() {
'use strict';
angular
.module('parentModule', [])
.component('parent', {
templateUrl: 'parent.tpl.html',
controller: ParentCtrl,
transclude: true,
bindings: {
item: '='
}
});
function ParentCtrl() {
var vm = this;
vm.item = {
'id': 1,
'name': 'test'
};
}
})();
And I'm simply trying to share the object item with another component, like this:
(function() {
'use strict';
angular
.module('childModule', [])
.component('child', {
templateUrl: 'child.tpl.html',
controller: ChildCtrl,
require: {
parent: '^item'
}
});
function ChildCtrl() {
console.log(this.parent)
var vm = this;
}
})();
View (Parent):
Parent Component:
<h1 ng-bind='$ctrl.item.name'></h1>
<child></child>
View (Child):
Child component:
Here I want to print the test that is in the parent component
<h2 ng-bind='$ctrl.item.name'></h2>
Actually I'm getting the following error:
Expression 'undefined' in attribute 'item' used with directive
'parent' is non-assignable!
Here's the DEMO to illustrate better the situation
Can you explain to me how I can make it work?
You need to remove the bindings from yor parent component.
bindings binds to the component controller like scope binds to a directive's scope. You're not passing anything to <parent></parent> So you have to remove it.
Then your child component requires a parent component, not an item.
So
require: {
parent: '^parent'
}
Of course the child template should be modified to:
<h2 ng-bind='$ctrl.parent.item.name'></h2>
Finally, if from the child controller you want to log the item that is inside the parent, you will have to write:
function ChildCtrl($timeout) {
var vm = this;
$timeout(function() {
console.log(vm.parent.item);
});
}
I never need the timeout in my components, so there might be something obvious that I missed.
http://plnkr.co/edit/0DRlbedeXN1Z5ZL45Ysf?p=preview
EDIT:
Oh I forgot, you need to use the $onInit hook:
this.$onInit = function() {
console.log(vm.parent.item);
}
Your child should take the item as input via bindings.
(function() {
'use strict';
angular
.module('childModule', [])
.component('child', {
templateUrl: 'child.tpl.html',
controller: ChildCtrl,
bindings: {
item: '='
}
});
function ChildCtrl() {
console.log(this.parent)
var vm = this;
}
})();
So your parent template will look like
<h1 ng-bind='$ctrl.item.name'></h1>
<child item="$ctrl.item"></child>
The rest should work same.

Bind to model from child directive where model has not been resolved at compile-time

I am using angular ui for bootstrap for its modals:
http://angular-ui.github.io/bootstrap/#/modal
I am opening a modal with a controller and templateUrl with:
var modalInstance = $uibModal.open({
animation: true,
templateUrl: $scope.templateUrl,
controller: $scope.controller,
size: 'lg',
resolve: {
formModel: item
}
});
where formModel is the model I will use in the modal.
Here is the controller for the modal:
app.controller('commentCtrl', ['$scope', '$modalInstance', 'formModel', function ($scope, $modalInstance, formModel) {
$scope.formModel = {};
var loadFormModel = function () {
if (formModel !== undefined) {
$scope.formModel = formModel;
}
};
loadFormModel();
}]);
This modal has child directives and needs to pass properties of formModel to them
template:
<div>
<child model="formModel.Comment"></child>
</div>
but child is created before the modal's controller has loaded formModel. Inside the child I want to use model as:
app.directive('child', function () {
return {
restrict: 'E',
template: '<textarea ng-model="model"></textarea>',
link: linkFn,
controller: controllerFn,
scope: {
model: '='
}
};
});
Edit:
I've found that I can do:
<div>
<child model="formModel" property="Comment"></child>
</div>
...
app.directive('child', function () {
return {
restrict: 'E',
template: '<textarea ng-model="model[property]"></textarea>',
link: linkFn,
controller: controllerFn,
scope: {
model: '=',
property: '#'
}
};
});
Is there a better way to do this without the extra attribute?
Edit 2:
I have found where the bug is:
http://plnkr.co/edit/kUWYDvjR8YArdqtQRHhi?p=preview
See fItem.html for some reason having any ng-if causes the binding to stop working. I have put a contrived ng-if='1===1' in for demonstration
This happens because ng-if directive creates new inherited scope, so the bug is just a common scope prototypal inheritance pitfall.
The most concise way to get around it is to use controllerAs syntax in conjunction with bindToController, the avoidance of undesirable scope inheritance side effects is the most common use case for them. So it will be
app.directive('fItem', function () {
return {
restrict: 'E',
replace: true,
templateUrl: 'fItem.html',
controller: ['$scope', function ($scope) {
}],
controllerAs: 'vm',
bindToController: true,
scope: {
model: '='
}
};
});
and
<div class="input-group">
<textarea ng-if='1===1' ng-model="vm.model" class="form-control"></textarea>
</div>
Not sure what you are trying to do. If you need just to wait until variable is resolved, you need just use promise: (Here is simple one using $timeout, if you use i.e. $http - you ofc dont need $q and $timeout)
resolve: {
something: function () {
return $q(function(resolve, reject) {
setTimeout(function() {
resolve('Hello, world!');
}, 3000);
});
Then modal will be opened only after promise is resolved.
http://plnkr.co/edit/DW4MzIO4ej0JorWRgWIK?p=preview
Also keep in mind that you can wrap you object, so if in scope you have $scope.object = {smth : 'somevalue'} :
resolve: {
object: function () {
return $scope.object;
});
And in modal controller:
$scope.object = object;
Now object in initial controller scope and object in modal scope point to same javascript object, so any time you change one - another changes. You are free to use object.smth in modal template as usual property. And as soon as it will change you will see changes.

Categories

Resources