I would like to ask you if it is possible, and if yes, then how can I pass some variable from controller to directive.
Here is a bit of my code:
app.js
var VirtualGallery = angular.module('virtualGallery', ['VirtualGalleryControllers', 'ngRoute']);
VirtualGallery.constant('apiURL', 'roomPicture');
VirtualGallery.run(['$rootScope', function ($rootScope) {
$rootScope.roomPictures = [];
}]);
var VirtualGalleryControllers = angular.module('VirtualGalleryControllers', ['ngRoute']);
VirtualGalleryControllers.controller('AppCtrl', function ($http, $rootScope, $scope, apiURL, $q) {
$scope.getallrooms = function () {
$http.get(apiURL)
.success(function (data) {
$rootScope.roomPictures = data; //idk whether to use $scope or $rootScope
});
};
});
In this app.js I'm trying to get some data from DB, and that data I need to use in directive.
Directive
angular.module('virtualGallery')
.directive('ngWebgl', function () {
return {
restrict: 'A',
scope: {
'getallrooms': '=',
'roomPictures': '='
},
link: function postLink(scope, element, attrs) {
scope.init = function () {
//here I would like to be able to access $scope or $rootScope from app.js file.
};
}
};
});
In directive I need to gain access to $scope or $rootScope in function init() where I need to use that data.
HTML
<body ng-app="virtualGallery">
<div class="container" ng-controller="AppCtrl">
<div
id="webglContainer"
ng-webgl
getallrooms="getallrooms"
roomPictures="roomPictures"
></div>
<p ng-model="roomPictures"></p>
<p ng-model="getallrooms"></p>
</div>
<script type="text/javascript" src="js/vg.js"></script>
<script type="text/javascript" src="js/ngWebgl.js"></script>
In html I'm trying to pass that data from app.js to directive.
Im quite new to Angular and this is even my first directive, so I am bit confused. Every help will be appreciated. Thanks guys :)
In your app.js use the controller like this
VirtualGalleryControllers.controller('AppCtrl', function ($http, $rootScope, $scope, apiURL, $q) {
$scope.getallrooms = function () {
$http.get(apiURL)
.success(function (data) {
$scope.roomPictures = data; //use $scope instead of $rootScope
});
};
});
Then for your directive:
angular.module('virtualGallery')
.directive('ngWebgl', function () {
return {
restrict: 'A',
scope: {
pictures: '=virtualGallery'
},
link: function postLink(scope, element, attrs) {
scope.init = function () {
// you can access the variable through the scope
scope.pictures;
};
}
};
});
Or you could simply make the http request in your directive and manipulate the data there.
You can inject $rootScope to your directive ( like you did in your controller ) and then access that rootScope variable.
Related
How can I access controller scope from multi-level directives in below structure:
I create a directive that has multi-level scopes inside its.
1. Controller scope
1.2. Directive1 scope(main directive)
1.2.1. Directive2 scope
1.2.1.1 Directive3 scope
I want to get the controller scope from directive 3.
please don't refer to $parent because the parent level it's not certain and a user may use this directive inside another directive.(see below codes)
<div ng-controller="Test">
<custom-dir>
<dir1>
<dir2>
<dir3>
</dir3>
</dir2>
</dir1>
<custom-dir>
</div>
The users create a function in the Test controller and the function will be called in my Directive 3 scope(how to get controller scope?).
<div ng-controller="Test">
<dir1>
<dir2>
<dir3>
</dir3>
</dir2>
</dir1>
</div>
More details(please don't refer to syntax error):
The controller is:
App.controller('ScopeController', function ($scope, $rootScope, $uibModal, $http, $filter, $cookieStore, Common, $cookies) {
$scope.runTest = function () {
return `<input type='button' ng-click='testHtml()' value='Test'/>`;
}
$scope.testHtml = function () {
alert("work");
}
$scope.model=someModel;
$scope.config=someConfig;
$scope.columns={html: $scope.runTest};
});
the dir1 directive:
App.directive("dir1", function ($compile, $filter, $rootScope, $timeout, Common, $window, $http) {
return {
restrict: "E",
priority: 1,
terminal: false,
templateUrl: "Content/html/Table.html?version=2.6",
scope: {
model: "=",
columns: "=",
config: "=",
search: "#",
},
link: function (scope, elem, attrs) {
scope.CallFunc = function (html) {
if (typeof (html) =="function" )
return html();
else {
return scope.$parent.$eval(html + "()", {});
}
}
}
}
});
the dynamic directive compile the runTest output
App.directive('dynamic', function ($compile) {
return {
restrict: 'A',
replace:true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(scope);
});
}
};
});
If I change the line $compile(ele.contents())(scope); to $compile(ele.contents())(scope.$parent.$parent); it's work.
In this directive, I need get the controller scope without $parent because
some users may use this directive inside another directive same below:
<custom-dir>
<dir1 model="model" columns="columns" config="config">
<div dynamic="CallFunc(columns.html)"></div>
</dir1>
</custom-dir>
The using HTML tag
<dir1 model="model" columns="columns" config="config">
<div dynamic="CallFunc(columns.html)"></div>
</dir1>
This issue handle with following codes:
A service for storing the controller scope:
App.service('TableService', function () {
return {
MyScope: null
};
});
Inject the TableService to dynamic directive(this directive compiles dynamic content):
App.directive('dynamic', function ($compile,TableService) {
return {
restrict: 'A',
replace:true,
link: function (scope, ele, attrs) {
scope.$watch(attrs.dynamic, function (html) {
ele.html(html);
$compile(ele.contents())(TableService.MyScope);
});
}
};
});
And finally in the controller:
App.controller('ScopeController', function ($scope, $rootScope, $uibModal,
$http, $filter, $cookieStore, Common, $cookies,TableService) {
TableService.myScope = $scope;
$scope.runTest = function () {
return `<input type='button' ng-click='testHtml()' value='Test'/>`;
}
$scope.testHtml = function () {
alert("work");
}
$scope.model=someModel;
$scope.config=someConfig;
$scope.columns={html: $scope.runTest};
});
After that, the dynamic directive can access controller scope and all dynamic events(like testHtml) will be called even if the directive put in another directive(without using the $parent).
thank you #shaunhusain, huan feng for giving me an idea.
In child controller do something like:
$scope.$broadcast('yourEvent');
In parent controller do the listener:
$scope.$on('yourEvent' , function(){
//Handle your logic
});
A special case service
.service('DirectDispatcher', function(){
return {
fireMe: angular.noop
}
});
First directive registers a function callback
.directive(
...
link:function(DirectDispatcher){
function myHandler() {window.alert('just testing')}
DirectDispatcher.fireMe = myHandler;
}
...
);
Second directive fires the function callback
.directive(
...
link:function(DirectDispatcher){
DirectDispatcher.fireMe();
}
...
);
I have a directive as the following:
app.directive('fileInput', ['$parse', function ($parse) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
element.bind('change', function () {
$parse(attrs.fileInput)
.assign(scope, element[0].files)
scope.$apply();
});
scope.$watch('files', function () {
console.log(scope.files);
});
},
controller: function ($scope, $element, $attrs) {
$element.bind('change', function () {
$parse($attrs.fileInput)
.assign($scope, $element[0].files)
$scope.$apply();
});
$scope.$watch('files', function () {
console.log($scope.files);
});
}
}
EDIT
and this is controller:
controllers.controller('RegisterCtrl', ['$scope', '$routeParams', '$location', '$http', 'Restangular', 'ServiceRepository',
function($scope, $routeParams, $location, $http, Restangular, ServiceRepository)
{
$scope.regService = function () {
$scope.error = {};
$scope.submitted = true;
var fd = new FormData();
fd.append("model", angular.toJson($scope.services));
console.log($scope.files);
}
}
And this is view file
<input type="file" id="boarding-picture_where_sleeping" class="form-control" file-input="files" multiple>
Additional info, regService() method is called when submitting the form
and when I debug $scope.files, it's available in console tab. but in my controller, it's undefined
so how to sync it between the directive and controller
Thanks
it's working now. the problem was caused I used 2 nested directives :)
Thanks guys
I would use scope and bindToController attributes in the directive definition, like in the following snippet from this blog:
app.directive('someDirective', function () {
return {
scope: {
name: '='
},
controller: function () {
this.name = 'Pascal';
},
controllerAs: 'ctrl',
bindToController: true,
template: '<div>{{ctrl.name}}</div>'
};
});
It requires use of the controllerAs syntax too, but you should be using that anyway as it is much more clear than passing $scope around everywhere and dealing with prototypical inheritance. This is recommended in John Papa's AngularJS Style Guide
I have the following code.
controller.js
angular.module('LiveAPP.main',['LiveAPP.factory'])
.controller('mainCtrl', ['$scope','$http', '$location','dataFactory',mainCtrl])
.directive('ratehome',function(){
return {
restrict:"E",
template: "<div id='rateYo'></div>",
link: function(scope, ele, attrs){
console.log("NEW",scope.recentArtist)
}
}
})
function mainCtrl($scope,$http,$location,dataFactory){
$scope.getRecentArtists = function(){
return $http({
method: 'GET',
url: '/artistsearch',
params: {getArtist: "all"}
}).then(function(recent){
$scope.recentArtist = recent.data
})
};
$scope.getRecentArtists();
$scope.recentArtist = ""
$scope.$watch('recentArtist',function(newValue,oldValue){
$scope.recentArtist = newValue
})
}
test.html
<ratehome></ratehome>
<ratehome></ratehome>
<ratehome></ratehome>
What happens here is upon instantiation of my controller(routing is set up correctly) there is a $http GET request that responds with data that I need that gets assigned to $scope.recentArtist. I want this data to be accessible in my link function, but it's not. I have a feeling that my directive is compiling before this request is sent. Is there any way around this? What is odd to me is that when I console.log(scope) and check in Chrome Developer Tools my data is there. Yet when I console.log(scope.recentArtist) its empty similar to its state in the controller. I was thinking I could maybe make the $http.get in my directive, but that seems a little awkward to me.
I have been having trouble with this problem for a few days, hopefully somebody can help me out.
If you're using angular ui-router you could also use resolve. With resolve you can do the $http before the controller starts.
You can use resolve to provide your controller with content or data that is custom to the state. resolve is an optional map of dependencies which should be injected into the controller.
If any of these dependencies are promises, they will be resolved and converted to a value before the controller is instantiated and the
$stateChangeSuccess event is fired.
from the docs.
Please have a look at the demo below or this jsfiddle.
angular.module('demoApp', ['ui.router'])
.config(function ($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider.state('home', {
url: '/',
template: '<ratehome></ratehome><ratehome></ratehome><ratehome></ratehome>',
controller: 'mainCtrl',
resolve: {
artists: function (artistsService) {
console.log('Resolve');
return artistsService.get(); //'/artistsearch',//artistsService.get();
}
}
});
})
.controller('mainCtrl', ['$scope', '$http', '$location', 'artists', MainCtrl])
.directive('ratehome', function () {
return {
restrict: "E",
template: '<div id="rateYo"><ul><li ng-repeat="artist in recentArtist">{{artist}}</li></ul></div>',
link: function (scope, elem, attrs) {
console.log("NEW", scope.recentArtist);
}
}
})
.factory('artistsService', function ($http) {
return {
get: function () {
console.log('getting');
return $http({
method: 'GET',
url: 'http://crossorigin.me/http://www.mocky.io/v2/558b30615f3dcbc414067170', //'/artistsearch',
//params: {getArtist: "all"}
}).then(function (recent) {
//console.log(recent);
return recent.data;
});
}
};
});
function MainCtrl($scope, $http, $location, artists) {
$scope.recentArtist = artists;
console.log('ctrl', artists);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<div ng-app="demoApp">
<div ui-view=""></div>
</div>
AS your directive is not using isolated scope, that does mean you can directly access your controller scope inside your directive. I'd suggest you to to put that $watch inside directive link instead of your controller. That would intimate you that the ajax has been completed and data got changed & you get those changed value inside watcher function.
Code
.directive('ratehome',function(){
return {
restrict:"E",
template: "<div id='rateYo'></div>",
link: function(scope, ele, attrs){
$scope.$watch('recentArtist',function(newValue,oldValue){
console.log("NEW",newValue)
});
}
}
})
Your link function is running before the $http response comes back as you suspect. You can wait for it by using $broadcast and $on:
angular.module('LiveAPP.main',['LiveAPP.factory'])
.controller('mainCtrl', ['$scope','$http', '$location','$rootScope','dataFactory',mainCtrl])
.directive('ratehome',function(){
return {
restrict:"E",
template: "<div id='rateYo'></div>",
link: function(scope, ele, attrs){
scope.$on('artistLoaded', function(){
console.log("NEW",scope.recentArtist);
});
}
};
});
function mainCtrl($scope,$http,$location,$rootScope,dataFactory){
$scope.getRecentArtists = function(){
return $http({
method: 'GET',
url: '/artistsearch',
params: {getArtist: "all"}
}).then(function(recent){
$scope.recentArtist = recent.data
$rootScope.$broadcast('artistLoaded');
});
};
$scope.getRecentArtists();
$scope.recentArtist = "";
$scope.$watch('recentArtist',function(newValue,oldValue){
$scope.recentArtist = newValue
});
}
This way the code will not run until the response has been returned and set
I have an app based on Angular which I initialize like this:
myapp.init = (function () {
'use strict';
var angularApp = angular.module('myapp', [])
.directive('homeIterationDirective', function () {
return function (scope, element, attrs) {
var isTopCard = scope.$last ? true : false;
cards.initSingleSwipe(element.get(0), function (event) {
// I want to call indexPageController.onSwiped(event) here!
}, isTopCard);
};
})
.directive('homeDirective', function () {
return function (scope, element, attrs) {
cards.initPanel(element, function (event) {
// I want to call indexPageController.onButtonPressed(event) here!
});
};
});
angularApp.factory('AjaxService', myapp.services.AjaxService);
angularApp.controller('IndexPageController', ['$scope', '$http', '$sce', 'AjaxService', myapp.pages.IndexPageController]);
}());
My controller looks like this:
myapp.pages.IndexPageController = function ($scope, $http, $sce, MyService) {
'use strict';
var somevalue = {};
this.onSwiped = function (event) {
doSomethingWith(event, somevalue);
};
this.onButtonPressed = function (event) {
doSomethingWith(event, somevalue);
};
};
In the 2 directives homeIterationDirective and homeDirective I have 2 callbacks cards.initSingleSwipe and cards.initPanel. Within these callbacks I want to call public methods of my controller but I don't have the instance available that Angular created from IndexPageController. How can I achieve this?
Use (inject) a service (and not a Controller) if you want "to call a public method" from another place, possibly from another Controller.
angularApp.controller('MyController', function ($scope, IndexPageService) {
IndexPageService.blah();
}));
Controller is intended to receive and modify a $scope (adding methods, variables..etc). The $scope can than be used inside the template (html) that use the controller itself.
If you want to call some controller code within the Angular context but in another place, then you should probably move that calling code to a service and then call the service method directly.
But if you want to call that method outside Angular context then, you can achieve that like this:
<div id="foo" ng-controller="IndexPageController">
<!-- code -->
</div>
Now, you can write something like this:
angular.element(document.getElementById("foo")).scope().blah();
I also think you should use a service for this. But if you still need to call one controller from another you can use $controller service
(function() {
var app = angular.module('myApp', ['ngRoute']);
app.config(function ($routeProvider) {
$routeProvider.when("/first", {
templateUrl: 'app/view.html',
controller: 'firstCtrl'
}).when("/second", {
templateUrl: 'app/view.html',
controller: 'secondCtrl'
})
.otherwise({
redirectTo: "/first"
});
});
app.controller('firstCtrl', function ($scope) {
$scope.name = "first Controller";
$scope.nameToUpper = function () {
return $scope.name.toUpperCase();
}
});
app.controller('secondCtrl', function ($scope, $controller) {
var newScope = $scope.$new();
$controller('firstCtrl', { $scope: newScope });
$scope.name = newScope.nameToUpper() + 'from second ctrl';
});
}())
and view is
<div>
{{name}}
</div>
I am creating a custom directive in a controller and calling it in ng-repeat as follows:
HTML:
<div ng-controller="TestCtrl">
<div ng-repeat="page in pages">
<custom
load-data="loadData = fn">
</custom>
</div>
</div>
JS:
In Test directive I am calling loadData as follows:
scope: {
loadData: "&"
}
controller: ['$scope', '$element', '$timeout', '$filter', function ($scope, $element, $timeout, $filter) {
$scope.loadData({ fn: function(data) {
//Data calc.
}});
}
I am calling loadData from TestCtrl as follows:
App.controller('TestCtrl', function($scope, $http, $timeout, $rootScope) {
$scope.loadData(data);
}
I need to call loadData function but it is throwing error as undefined is not a function
Is there any way I can access scope of child directive from outside it. I went through few SO questions where it was mentioned that using ng-repeat changes scope, but I was not able to figure out a solution to it. I also tried using $broadcast and $on but that did not help
Could anyone please help me with it.
Thanks in Advance
I'm not sure to understand your request.. Your code doesn't make any sense, where is defined your data variable ? (controller: line 2), where is defined your fn function ? (html: line 4).
You got the error undefined is not a function which not surprising me because you never defined $scope.loadData method...
I tried to understand your question and produced this code snippet :
angular.module('demo', []);
angular.module('demo')
.directive('custom', function () {
return {
restrict: 'E',
template: '<div>{{ data || "loading..." }}</div>',
scope: {
loadData: '=',
page: '='
},
link: function (scope) {
scope.loadData(scope.page, function (data) {
scope.data = data;
});
}
};
});
angular.module('demo')
.controller('MainCtrl', function ($scope, $timeout) {
$scope.loadData = function (page, done) {
$timeout(function () {
done('data loaded data from ' + page.name);
}, 1000);
};
$scope.pages = [
{ name: 'page 1' },
{ name: 'page 2' },
{ name: 'page 3' }
];
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.js"></script>
<div ng-app="demo" ng-controller="MainCtrl">
<div ng-repeat="page in pages">
<custom load-data="loadData" page="page"></custom>
</div>
</div>
Maybe this could help you.