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"
})
Related
I have an angular 1.5 component (i.e. myCarousel: it wraps a bootstrap carousel) that triggers an event and exposes it with a function binding.
A parent container (i.e. myContainer) component binds a function to that event and modifies some inner state when triggered.
Another child-component of the parent container is bound to the container's inner state.
The problem:
When I trigger the event on myCarousel from a button click event, everything works fine and I can see the child-component's binding being updated.
However, when I trigger the event from a carousel slide event (slide.bs.carousel), the binding does not get updated.
Here's a plunker to demonstrate:
https://plnkr.co/edit/AHxaX8o0sE94Nfir7DVB
Can anyone explain why this is happening and how to solve this?
Some relevant code:
mainApp.component("myCarousel", {
templateUrl: "myCarousel.html",
bindings: {
onEventTriggered: "&"
},
controllerAs: "vm",
controller: function() {
let vm = this;
vm.$onInit = function() {
console.log("init!");
$("#theCarousel").carousel();
$("#theCarousel").on("slide.bs.carousel", (event) => {
console.log("sliding " + event.direction);
vm.onEventTriggered();
});
};
}
});
mainApp.component("myContainer", {
templateUrl: "myContainer.html",
controllerAs: "vm",
controller: function() {
let vm = this;
vm.counter = 0;
vm.triggerEvent = function() {
console.log("event!");
vm.counter++;
}
}
});
mainApp.component("childComponent", {
template: "<div>Event {{vm.attribute}}</div>",
controllerAs: "vm",
bindings: {
attribute: "<"
}
});
One way to do this is :
controller: function($scope) {
let vm = this;
vm.$onInit = function() {
console.log("init!");
$("#theCarousel").carousel();
$("#theCarousel").on("slide.bs.carousel", (event) => {
vm.onEventTriggered();
console.log("sliding " + event.direction);
$scope.$apply();
});
};
}
using $scope.$apply()
updated plunkr : https://plnkr.co/edit/8yZOyuQfofdoYz00tFBk?p=preview
You need to use $scope.$apply(), because $("#theCarousel").on("slide.bs.carousel", ... is jquery code, and you need to notify angular about it using $scope.$apply()
I am trying to pass data to a ubi modal that is an Angular 1.5 component using resolve. I know this is possible because it shows that resolve is supported for components in the uib modal documentation.
component (Type: string, Example: myComponent) - A string reference to
the component to be rendered that is registered with Angular's
compiler. If using a directive, the directive must have restrict: 'E'
and a template or templateUrl set.
It supports these bindings:
(...)
resolve - An object of the modal resolve values. See UI Router
resolves for details.
All the examples I am finding declare templateurl/controller in the open method. Then the item declared in resolve is injected into to the controller. I am passing an Angular 1.5 component to the modal (not templateurl/controller), and when I try to inject the item from resolve, I get a dreaded "unknow provider" error.
Here is my code. I am trying to pass a url.
Controller of component calling the model
ParentController.$inject = ['$uibModal'];
function ParentController $uibModal) {
var $ctrl = this;
$ctrl.openComponentModal = function(url) {
var modalInstance = $uibModal.open({
animation: false,
component: "ImageModalComponent",
resolve: {
url: function() {
return url;
}
}
});
};
}
Controller in component that is the modal
ImageModalController.$inject = ['url'];
function ImageModalController(url) {
var $ctrl = this;
$ctrl.$onInit = function() {
console.log(url);
};
}
For components, resolve needs to be added to the bindings, then it is available on the isolated scope. In other words, add resolve: '<' when declaring the component.
Modal Component
var template = require('./image_modal.component.html');
var ImageModalController = require('./image_modal.controller');
module.exports = {
template: template,
bindings: {
close: '&',
resolve: ‘<‘
},
controller: ImageModalController
};
Controller of component calling the model
ParentController.$inject = ['$uibModal'];
function ParentController $uibModal){
var $ctrl = this;
$ctrl.openComponentModal = function (urlFromClickEvent) {
var modalInstance = $uibModal.open({
animation: false,
component: "ImageModalComponent",
resolve: {
url: function() {
return url;
}
}
});
};
}
Controller of component that is the modal
ImageModalController.$inject = [];
function ImageModalController() {
var $ctrl = this;
$ctrl.$onInit = function () {
console.log($ctrl.resolve.url);
};
}
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
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 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.