I'm working on a angularjs project and I'm trying to implement it as close as possible to angularjs 2.
In angularjs 2 there will be no more ng-controller and $scope, so I'm using directives instead of ng-controller. Also I'm trying to avoid the use of $scope by using the controllerAs-syntax.
My directives would look something like this:
angular.module('myApp').directive('myDirective', function() {
return {
controller: function() {
this.data = '123';
},
controllerAs: 'ctrl',
template: ' <p>{{ctrl.data}}</p> '
};
});
So far everything works fine. The problems start when I'm trying to call a methode or a property on the controller within a callback-function. In this case 'this' is not referencing the actual controller-instance any more.
Example:
angular.module('myApp').directive('myDirective', function($http) {
return {
controller: function() {
this.data = '';
this.loadData = function(){
$http.get("someurl.com")
.success(function(response) {
this.data = response.data; // --> this doesn't work
});
}
this.loadData();
},
controllerAs: 'ctrl',
template: ' <p>{{ctrl.data}}</p> '
};
});
Is there a way to get a reference to my controller-instance within the callback so I can use the controllers methods and properties within the callback?
What you need to do is store a reference to this in a variable at the beginning of the controller...then add your properties to that variable. Context won't be lost inside callbacks this way.
A common convention is to use var vm for "View Model" but naming of variable is subjective
angular.module('myApp').directive('myDirective', function($http) {
return {
controller: function() {
var vm = this;
vm.data = '';
vm.loadData = function(){
$http.get("someurl.com")
.success(function(response) {
vm.data = response.data;
});
}
vm.loadData();
},
controllerAs: 'ctrl',
template: ' <p>{{ctrl.data}}</p> '
};
});
A popular angular style guide
Related
HTML:
<teacher-modal modal-type="edit" file-name="teacherModal"
button-title="DerecEdit" modal-value="teacher"
button-class="" message="teacherCtrl.message">
</teacher-modal>|
{{teacherCtrl.message}}
Directive:
(
function(){
angular.module('MockApp')
.directive('teacherModal',teacherModal);
function teacherModal(){
var directive = {
restrict: 'EA',
scope:{
'type': '#modalType',
'title': '#buttonTitle',
'class': '#buttonClass',
'fileName': '#fileName',
modalValue: '=',
message: '=',
},
template:'<a type="button" class="{{class}}" ng-click="modalDCtrl.open(modalValue,fileName)">{{title}}</a>',
controller:'modalDirectiveController as modalDCtrl'
}
return directive;
}
angular.module('MockApp')
.controller('modalDirectiveController', modalDirectiveController);
modalDirectiveController.$inject = ['$uibModal','TeacherService','$scope', '$rootScope'];
function modalDirectiveController($uibModal , TeacherService , $scope, $rootScope){
var vm = this;
vm.open = function(teacher,fileName){
var msg;
console.log('open function of teacher modal ctrl' + fileName);
var modalInstance = $uibModal.open({
ariaLabelledBy: 'modal-title',
ariaDescribedBy: 'modal-body',
templateUrl: 'components/modalComponent/teacher/'+fileName+'.html',
controller: 'TeacherModalController as teacherModalCtrl',
size: 'md',
resolve:{
newTeacher:function(){
return teacher;
}
}
});
modalInstance.result.then(function (result) {
console.log('result of directive controller');
if(result.actionType == 'Add Teacher')
$scope.message = result.teacherName + " has been added";
else
$scope.message = result.teacherName + " has been edited";
//<!-- The above $scope.message is not being viewed but I want to show this message -->
$rootScope.$broadcast("updateMessage", msg);
}, function () {
console.info('Modal dismissed at: ' + new Date());
});
// <!-- This $scope is being viewed but I dont want to show this message -->
//$scope.message = 'hello from teacher-modalnn.js';
}
}
}
)();
$scope.message inside the modalInstance.result.then() function is not previewing whereas the $scope.message outside the function is being viewed.
When we use the $rootScope.broadcast() the message is being previewed, but I would not like to use the $broadcast service.
Github URL: https://github.com/coderabin01/ngStudentApp
Can anyone answer me why and how?
You are applying a value outside out of the angular context (XHR call), so you need to use $apply().
$apply() is used to execute an expression in AngularJS from outside of
the AngularJS framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the AngularJS framework we need to perform proper scope life cycle
So try this:
...
$scope.$apply(function() {
$scope.message = result.teacherName + " has been added";
});
...
I have an angularJS (1.5+) component that has some one-way binding properties that are bound to a parent controller's variables. This component uses those properties in the bindings object directly and doesn't need to set up any local variables.
When the page loads, the component's bindings are initialized and bound to default values because the parent controller initializes its variables to default values.
example code:
App.component('myComponent',{
bindings:{
binding_1: '<',
binding_2: '<'
},
template:'<div ng-show="$ctrl.binding_1">' +
'<input type="button" ng-click="$ctrl.clicked()">' +
'</div>',
controller: function () {
var ctrl = this;
// would ctrl.$onInit = function(){...} be beneficial here at all?
ctrl.clicked = function(){
console.log("ctrl.binding_2.text");
}
});
If the component only uses its binding properties and those properties are initialized to default values via the parent controller variables upon page load, then what would be the benefit(s) of implementing $onInit and where would I expect to see this (these) benefit(s)?
$onInit is the component's initialization lifecycle hook. You should perform your initialization logic there. In the case of your code sample, the component bindings would likely be accessible since they are being accessed in a click handler. Here is a small demo demonstrating $onInit.
Component Initialization with $onInit
angular.module('app',[])
.controller("MyController", function(){
var ctrl = this;
ctrl.title = "Hello World";
})
.component("myComponent", {
bindings:{
bindingOne: '<'
},
template: ' <h1>{{ctrl.title}}</h1>',
controller: function(){
this.$onInit = function(){
this.title = this.bindingOne;
}
},
controllerAs: "ctrl"
})
Component Initialization Without $onInit
angular.module('app',[])
.controller("MyController", function(){
var ctrl = this;
ctrl.title = "Hello World";
})
.component("myComponent", {
bindings:{
bindingOne: '<'
},
template: ' <h1>{{ctrl.title}}</h1>',
controller: function(){
this.title = this.bindingOne;
},
controllerAs: "ctrl"
})
I have a directive that only loads a template like this:
app.directive("sidebar", function(RESOURCE_URL) {
return {
templateUrl: RESOURCE_URL + 'sidebar.html'
};
});
The HTML looks like this:
<sidebar ng-controller="sidebarCtrl" ng-show="ready"></sidebar>
And the controller:
app.controller('sidebarCtrl', function($scope, authService) {
$scope.service = authService;
$scope.ready = false;
$scope.user = {};
$scope.$watch('service.getUser()', function(value){
$scope.user = value;
$scope.ready = true;
});
});
Is there a way to simply make the directive use the controller's scope variables? Or what's the common method to use in this case?
There are 2 main ways to access some scope variables inside your directive,
the first one is to enable scope inheritance inside of your directive by using scope: true
app.directive("sidebar", function(RESOURCE_URL) {
return {
scope: true,
templateUrl: RESOURCE_URL + 'sidebar.html'
};
});
which allows you to inherit outer scope inside of your directive, the other way is to attach the controller to your directive by using controller: sideBarCtrl:
app.directive("sidebar", function(RESOURCE_URL) {
return {
controller: 'sideBarCtrl',
templateUrl: RESOURCE_URL + 'sidebar.html'
};
});
or, you can write a service to hold your scope variables and this will allow you access from different parts of your code to the same instance of the variable.
https://docs.angularjs.org/guide/services
I have an html file with a controller and a directive with a template url. I want to load/compile the directive conditionally in the controller:
Controller:
app.controller('TestController', function TestController($http, $scope, $compile) {
$scope.loadData = function (pageId) {
var pUrl = <some url>
$http({
method: 'GET',
url: pUrl
}).success(function (data, status) {
$scope.pData = data;
var htm = '<test-directive></test-directive>';
var elm = angular.element("#id").append(htm);
$compile(elm)($scope);
}).error(function (data, status) {
alert('error');
});
};
$scope.loadData();
});
Directive:
'use strict';
app.directive('testdirective', function ($http) {
var uDirective = {};
uDirective.restrict = 'E';
uDirective.templateUrl = 'js/directives/testdirective.html';
uDirective.controller = function ($scope, $element, $attrs) {
$scope.showDirectiveData();
$scope.showDirectiveData = function () {
$scope.directiveDataCollection = <get data>;
};
};
uDirective.compile = function (element, attributes) {
// do one-time configuration of element.
var linkFunction = function ($scope, element, atttributes) {
};
return linkFunction;
};
return uDirective;
});
Template used in Directive
<div>
<div ng-repeat="directiveData in directiveDataCollection">
<span><h4>{{directiveData.Title}}</h4></span>
</div>
</div>
How do i get to compile the code in the TestController, load the directive dynamically, and finally load the content and append the content in scope?
Here is a general template for you to reference that abstracts and also demonstrates a few Angular concepts:
JS
.directive('parentDirective', function(Resource, $compile){
return {
restrict: 'E',
link: function(scope, elem, attrs){
Resource.loadData().then(function(result){
scope.data = result.data;
var htm = '<child-directive></child-directive>';
var compiled = $compile(htm)(scope);
elem.append(compiled);
});
}
}
})
.directive('childDirective', function(){
return {
restrict: 'E',
template: '<div>Content: {{data.key}}</div>'
}
})
.factory('Resource', function($http){
var Resource = {};
Resource.loadData = function(){
return $http.get('test.json');
}
return Resource;
})
HTML
<body ng-app="myApp">
<parent-directive></parent-directive>
</body>
Notice that there is no controller code. This is because controllers should never manipulate the DOM - one reason is that it will make your code a PITA to test. So, I put everything in directives, where it should probably be in your case as well.
I also moved the $http service into a factory. Anything state/model related should be in a service. Among other reasons, by doing this, you can inject it almost anywhere (including inside of directives) to access your data without worrying about it disappearing when a controller unloads.
EDIT
You should also consider the dissenting view of the dynamic loading approach in general in the accepted answer of Dynamically adding Angular directives
I want to create an AlertFactory with Angular.factory.
I defined an html template like follow
var template = "<h1>{{title}}</h1>";
Title is provided by calling controller and applied as follow
var compiled = $compile(template)(scope);
body.append(compiled);
So, how I can pass isolated scope from controller to factory?
I'm using in controller follow code
AlertFactory.open($scope);
But $scope is global controller scope variable. I just want pass a small scope for factory with just title property.
Thank you.
You can create a new scope manually.
You can create a new scope from $rootScope if you inject it, or just from your controller scope - this shouldn't matter as you'll be making it isolated.
var alertScope = $scope.$new(true);
alertScope.title = 'Hello';
AlertFactory.open(alertScope);
The key here is passing true to $new, which accepts one parameter for isolate, which avoids inheriting scope from the parent.
More information can be found at:
http://docs.angularjs.org/api/ng.$rootScope.Scope#$new
If you only need to interpolate things, use the $interpolate service instead of $compile, and then you won't need a scope:
myApp.factory('myService', function($interpolate) {
var template = "<h1>{{title}}</h1>";
var interpolateFn = $interpolate(template);
return {
open: function(title) {
var html = interpolateFn({ title: title });
console.log(html);
// append the html somewhere
}
}
});
Test controller:
function MyCtrl($scope, myService) {
myService.open('The Title');
}
Fiddle
Followings are the steps:
Add your HTML to the DOM by using var comiledHTML =
angular.element(yourHTML);
Create a new Scope if you want var newScope = $rootScope.$new();
Call $comile(); function which returns link function var linkFun =
$compile(comiledHTML);
Bind the new scope by calling linkFun var finalTemplate =
linkFun(newScope);
Append finalTemplate to your DOM
YourHTMLElemet.append(finalTemplate);
check out my plunkr. I'm programmatically generating a widget directive with a render directive.
https://plnkr.co/edit/5T642U9AiPr6fJthbVpD?p=preview
angular
.module('app', [])
.controller('mainCtrl', $scope => $scope.x = 'test')
.directive('widget', widget)
.directive('render', render)
function widget() {
return {
template: '<div><input ng-model="stuff"/>I say {{stuff}}</div>'
}
}
function render($compile) {
return {
template: '<button ng-click="add()">{{name}}</button><hr/>',
link: linkFn
}
function linkFn(scope, elem, attr) {
scope.name = 'Add Widget';
scope.add = () => {
const newScope = scope.$new(true);
newScope.export = (data) => alert(data);
const templ = '<div>' +
'<widget></widget>' +
'<button ng-click="export(this.stuff)">Export</button>' +
'</div>';
const compiledTempl = $compile(templ)(newScope);
elem.append(compiledTempl);
}
}
}
I assume when you are talking about an isolate scope you are talking about a directive.
Here is an example of how to do it.
http://jsfiddle.net/rgaskill/PYhGb/
var app = angular.module('test',[]);
app.controller('TestCtrl', function ($scope) {
$scope.val = 'World';
});
app.factory('AlertFactory', function () {
return {
doWork: function(scope) {
scope.title = 'Fun';
//scope.title = scope.val; //notice val doesn't exist in this scope
}
};
});
app.controller('DirCtrl', function ($scope, AlertFactory) {
AlertFactory.doWork($scope);
});
app.directive('titleVal',function () {
return {
template: '<h1>Hello {{title}}</h1>',
restrict: 'E',
controller: 'DirCtrl',
scope: {
title: '='
},
link: function() {
}
};
});
Basically, attach a controller to a directive that has defined an isolate scope. The scope injected into the directive controller will be an isolate scope. In the directive controller you can inject your AlertFactory with wich you can pass the isolate scope to.