how to pass scope variables from controller to directive - angularjs

I want to pass the scope variables defined in the 'MapCtrl' to the 'mapCanvas' directive. As the code stands the console.log returns undefined. Does anyone know how I can pass the variables from my controller to my directive? Thanks.
angular.module('Ski').controller('MapCtrl', function($scope, $http) {
'use strict';
$http.get('https://quiet-journey-8066.herokuapp.com/mountains/5').success(function(response) {
console.log(response);
$scope.mountain = response.name;
$scope.lat = response.latitude;
$scope.lng = response.longitude;
});
});
angular.module('Ski').directive('mapCanvas', function() {
return {
link: function(scope, element) {
console.log(scope.lat)
console.log(scope.lng)
console.log(scope.mountain)
};
}
}

Related

angularjs 1.7.2 - How to call a directive function from a controller?

How do you call a directive's function from within a controller?
I have seen a number of answers to this question, with the solution similar to the following:
https://lennilobel.wordpress.com/tag/calling-directive-from-controller/
I have implemented it as follows:
Directive
angular.module('myApp').directive('myDirective', ['$log',
function ($log) {
function link(scope, element, attributes) {
//function that a controller can call
scope.myFunc = function () {
//Do Something
};
//if the user has provided an accessor, attach the function
if (scope.accessor) {
scope.accessor.myFunc = scope.myFunc;
}
}
return {
link: link,
restrict: 'E',
templateUrl: 'app/myTemplate.html',
scope: {
accessor: '=',
}
}
}
Controller
angular.module('myApp').controller('myCtrl', ['$log', '$q',
function ($log, $q) {
var vm = this;
// empty object that the directive will attach myFunc to
vm.accessor = {};
$q
.all([
//Service Call
])
.then(function (results) {
//manipulate the results of the service call using the
//directives function
vm.callDirective();
},
function (err) {
$log.debug('$q.all err:', err);
});
vm.callDirective = function () {
if (vm.accessor.myFunc) {
vm.accessor.myFunc();
} else {
$log.error('umm, I don\'t have a function to call');
}
};
}
HTML Template
<div ng-controller="myCtrl">
<myDirective accessor="vm.accessor"></myDirective>
</div>
When I run the code, the directive indicates that accessor is undefined. As a result, accessor, in the controller, doesn't have myFunc defined.
How do I get myFunc to execute?
I am using angular 1.7.2
The controller is compiled (an instance created with the resulting scope) before the directive.
In this scenario, it compiles faster than the directive can set the accessor function.
A quick workaround for this is to set a delay before checking if there is an accessor present using $timeout service.
The key is having a Promise object passed to $q.all. This will cause a small delay and allowing for the directive to be compiled.
For real, you'll be having promises that do some network call passed to $q.all instead of doing this workaround with the $timeout service.
Here is how this will go:
index.html
<div ng-controller="myCtrl as vm">
<my-directive accessor="vm.accessor"></my-directive>
</div>
script.js
const myApp = angular.module('myApp', []);
myApp.directive('myDirective', ['$log', myDirective]);
myApp.controller('myCtrl', ['$scope', '$timeout', '$log', '$q', myCtrl]);
function myCtrl($scope, $timeout, $log, $q) {
const vm = $scope.vm;
// empty object that the directive will attach myFunc to
vm.accessor = {};
vm.callDirective = () => {
if (vm.accessor.myFunc) {
vm.accessor.myFunc();
} else {
$log.error("umm, I don't have a function to call");
}
};
const handleSuccess = results => {
//manipulate the results of the service call using the
//directives function
vm.callDirective();
};
const handleError = err => {
$log.debug('$q.all err:', err);
};
$q.all([
//Service Call
$timeout()
])
.then(handleSuccess)
.catch(handleError);
}
function myDirective($log) {
//function that a controller can call
const myFunc = function() {
//Do Something
$log.info('Calling assessor myFunc');
};
const link = function(scope) {
//if the user has provided an accessor, attach the function
if (scope.accessor) {
scope.accessor.myFunc = myFunc;
}
};
return {
link: link,
restrict: 'E',
templateUrl: 'mydirective.html',
scope: {
accessor: '='
}
};
}

Passing scope data from a function to parent scope?

In my controller I have a $http call which returns a json string which I then want to pass to a directive to be added to a map. The string is being passed from the controller to the directive fine but not from from the $http function within the controller to the directive.
wmm.controller('wapMapClr', ['$rootScope', '$scope', '$window', '$http', function ($rootScope, $scope, $window, $http) {
$scope.geobj = {};
$scope.geobj.geoprop = ""
// Search by postcode
// create a blank object to hold our form information
$scope.formData = {};
$scope.pcSearch = function () {
$scope.data = {};
$http.post('api/api.php', { postcode: $scope.formData } )
.success(function (result) {
$scope.geobj = {geoprop : result.json_string};
console.log($scope.geobj.geoprop);
Any help would really appreciated. Thanks
Promises are asynchronous, so you don't know when the promise returns, so it won't be immediately available for you
Your directive has a controller method, from where you can fire the $http call which you can access.
You can use $emit/$brodcast to listen to events passed from controller to your directive.
I am not sure what error you get, here is a fiddle with $timeout used which is async which works.
var myApp = angular.module('myApp',[]);
myApp.directive('passObject', function() {
return {
restrict: 'E',
scope: { obj: '=' },
template: '<div>Hello, {{obj.prop}}!</div>'
};
});
myApp.controller('MyCtrl', function ($scope, $timeout) {
$scope.obj = { prop: "world" };
$timeout(function(){
$scope.obj = { prop: "from timeout" };
},10000);
});
https://jsfiddle.net/jt6j82by/
Thanks Thalaivar. I modified the code you gave and it worked. See below:
wmm.controller('wapMapClr', ['$scope', '$window', '$http', function ($scope, $window, $http) {
$scope.geobj = {};
// Search by postcode
// create a blank object to hold our form information
$scope.formData = {};
$scope.pcSearch = function () {
$scope.data = {};
$http.post('api/api.php', { postcode: $scope.formData } )
.success(function (result) {
$scope.geobj = {geoprop : result.json_string};
Then in the directive...
wmm.directive('tchOlMap', function () {
var MAP_DOM_ELEMENT_ID = 'tchMap';
return {
restrict: 'E',
//BELOW IS THE LINE I CHANGED TO MAKE IT WORK!
scope: false,
replace: true,
template: '<div id="' + MAP_DOM_ELEMENT_ID + '" class="full-height"></div>',
link: function postLink(scope, element, attrs) {

Unit Testing $routeParams in directive

I have a directive that accesses the $routeParams of the page as such:
myApp.directive("myList", function ($routeParams) {
return {
restrict: 'E',
templateUrl: 'tabs/my-list.html',
link: function (scope) {
scope.year = $routeParams.year;
}
};
});
The directive works as expected and correctly accesses the $routeParams
I am trying to test using angular-mock/jasmine. I can't figure out how to pass mock $routeParams to the directive. This is what I have:
describe('myList', function () {
var scope, compile, element, compiledDirective;
var mockParams = { 'year': 1996 };
beforeEach(function () {
module('templates', 'MyApp');
inject(function ($compile, $rootScope, $routeParams) {
compile = $compile;
scope = $rootScope.$new();
});
element = angular.element('<my-list></my-list>');
compiledDirective = compile(element)(scope);
scope.$digest();
});
it('should fill in the year', function () {
expect(scope.year).toEqual(mockParams.year);
});
});
Which obviously doesn't work because I never passed passed mockParams to the directive. Is there a way to do this?
Mock the $routeParams object mockParams using angular.extend OR do assign mockParams object directly to $routeParams. In that way $routeParams will be available before directive gets compiled.
inject(function ($compile, $rootScope, $routeParams) {
compile = $compile;
scope = $rootScope.$new();
angular.extend($routeParams, mockParams);
});

Using AngularJS service to update scope in other controller from other module

I am a newbie for AngularJS so maybe I am looking at this the wrong way. If so, please point me in the right direction.
Basically I want to update some DOM elements that reside in another controller in another module.
I am trying to send data through a service but it seems that it is not updated on the destination scope.
var mainModule = angular.module('main', []);
var appModule = angular.module('app', ['main']);
appModule.controller("appCtrl", function ($scope, $routeParams, mainService) {
$scope.mainService = mainService;
var initialize = function () {
$scope.mainService.currentID = $routeParams.productId;
}
initialize();
});
mainModule.factory('mainService', function () {
var mainService = { currentID: 0 };
return mainService
});
mainModule.controller('mainCtrl', ['$scope', 'mainService', function ($scope, mainService) {
$scope.mainService = mainService;
$scope.function1Url = "function1/" + $scope.mainService.currentID;
$scope.function2Url = "function2/" + $scope.mainService.currentID;
//CurrentID is always 0!!
}]);
I expect that when calling the initialize() function in the appCtrl, it will see the currentID param in the service which is also used by the mainCtrl.
For updating controller using service, I strongly recommend you to use $rootScope.$broadcast and $rootScope.$on. Here is an example of how you can do it, and link to a blog:
$rootScope.$broadcast('myCustomEvent', {
someProp: 'Sending you an Object!' // send whatever you want
});
// listen for the event in the relevant $scope
$rootScope.$on('myCustomEvent', function (event, data) {
console.log(data); // 'Data to send'
});
http://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/
Here is your working solution:
var mainModule = angular.module('main', []);
var productModule = angular.module('product', ['main']);
productModule.service('mainService', ['$rootScope', '$timeout', function ($rootScope, $timeout) {
this.method1 = function () {
alert('broadcast');
$rootScope.$broadcast('myCustomEvent', {
newValue: 'This is an updated value!'
});
}
}]);
productModule.controller('mainCtrl', ['$scope', '$rootScope', function ($scope, $rootScope){
$scope.myValue = 'Initial value';
$rootScope.$on('myCustomEvent', function (event, data) {
$scope.myValue = data.newValue;
alert('received broadcast');
});
}]);
productModule.controller("productCtrl", function ($scope, mainService) {
$scope.mainService = mainService;
$scope.clickMe = 'Click to send broadcast';
$scope.callService = function () {
$scope.clickMe = 'Broadcast send!';
$scope.mainService.method1();
}
});
And HTML:
<body ng-app='main'>
<div ng-controller="mainCtrl"><b>My value:</b>{{myValue}}</div>
<div id="product" ng-controller="productCtrl">
<button ng-click="callService()">{{clickMe}}</button>
</div>
<script type="text/javascript">angular.bootstrap(document.getElementById("product"), ['product']);</script>
</body>
You have a couple different methods of doing this.
I agree with uksz, you should use broadcast/emit to let other scopes know of the change, let them handle as needed.
Broadcast goes to all child scopes of the element
$scope.$broadcast("Message Name", "payload, this can be an object");
Emit goes to all parent scopes of this element
$scope.$emit("message name", "payload, this can be an object");
Other option is you can also require the other controller
appModule.directive('myPane', function() {
return {
require: '^myTabs',
scope: {},
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
}
};
});
Lastly you can include a function on the scope so you can let the parent scope know what's going on
appModule.directive('myPane', function() {
return {
scope: {
doSomething: '&something'
},
link: function(scope, element, attrs) {
$scope.doSomething(test);
}
};
});

Using jasmine to test angular directive that uses a controller

I am trying to write a jasmine test that will test if an angular directive I've written is working.
Here is my spec file:
describe('blurb directive', function () {
var scope, httpMock, element, controller;
beforeEach(module('mdotTamcCouncil'));
beforeEach(module('mdotTamcCouncil.core'));
beforeEach(module('blurb'));
beforeEach(inject(function (_$httpBackend_, $rootScope, $compile) {
element = angular.element('<mcgi-blurb text-key="mainPageIntro"></mcgi-blurb>');
var httpResponse = '<textarea name="content" ng-model="content"></textarea>';
scope = $rootScope.$new();
httpMock = _$httpBackend_;
httpMock.whenGET('components/blurb/blurb.html').respond(httpResponse);
element = $compile(element)(scope);
scope.$digest();
}));
it('should have some content', function () {
expect(scope.content).toBeDefined();
});
});
The value "scope.content" is always undefined and when I look at the scope object it seems to be a generic scope object that doesn't have my custom attributes on it.
Here are the other related files:
blurb-directive.js
(function () {
'use strict';
angular.module('blurb')
.directive('mcgiBlurb', blurb);
function blurb() {
return {
restrict: 'E',
replace: true,
templateUrl: jsGlobals.componentsFolder + '/blurb/blurb.html',
controller: 'BlurbController',
controllerAs: 'blurb',
bindToController: false,
scope: {
textKey: "#"
}
};
};
})();
blurb-controller.js
(function () {
angular.module('blurb')
.controller('BlurbController', ['$scope', 'blurbsFactory', 'userFactory', function ($scope, blurbsFactory, userFactory) {
$scope.content = "";
$scope.blurbs = {};
$scope.currentUser = {};
this.editMode = false;
userFactory().success(function (data) {
$scope.currentUser = data;
});
blurbsFactory().success(function (data) {
$scope.blurbs = data;
$scope.content = $scope.blurbs[$scope.textKey];
});
this.enterEditMode = function () {
this.editMode = true;
};
this.saveEdits = function () {
this.editMode = false;
$scope.blurbs[$scope.textKey] = $scope.content;
};
}]);
})();
What am I doing wrong?
The directive has isolated scope, so the scope passed to its controller and link function (if there was one), is the isolated one, different than your scope.
You may have luck getting the scope of the directive using element.isolateScope(); you may not, because of the replace: true - try to make sure. You may also access the controller instance using element.controller('mcgiBlurb').

Resources