How to use bindings in controller in an AngularJs 1.6 component - angularjs

Hello here is my component :
angular.module('myApp').component('dendroCtrl', {
templateUrl: '/templates/dendro.html',
bindings: {
id: '=',
type: '=',
mini: "="
},
controller: function ($scope, Api) {
//Dendro
var test = mini;
I tried this :
var test = mini;
var test = this.mini;
var test = $scope.mini;
Mini is everytime undefined.
How could I use my bindings in my controller ? Thanks
EDIT
This is how I call the component :
<dendro-ctrl id="149" type="Demand" mini="false"></dendro-ctrl>

From the documentation:
Components have a well-defined lifecycle Each component can implement "lifecycle hooks". These are methods that will be called at certain points in the life of the component. The following hook methods can be implemented:
$onInit() - Called on each controller after all the controllers on an element have been constructed and had their bindings initialized (and before the pre & post linking functions for the directives on this element). This is a good place to put initialization code for your controller.
$onChanges(changesObj) - Called whenever one-way bindings are updated. The changesObj is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form
So you can just use:
controller: function(Api) {
var ctrl = this;
ctrl.$onInit = function() {
console.log(ctrl.mini);
}
}
if you want to display the initial value of the mini binding.

Related

Two way data bindings does not trigger $onChanges in Components

Two way data bindings not updating between components
I am setting up inter component communication using two way data binding. I have one parent controller which fetches data from AJAX call and sends that data to 2 components.
I have tried to modify the data that is passed to the components, but if child1 component updates the data, child component is not getting the update data though the two way data binding is present. I read that $onChanges hook will not capture the change event for two way data binding.
<div ng-controller="ParentController as ctrl">
<child1 data="ctrl.data"></child1>
<child2 data="ctrl.data"></child>
</div>
Parent Controller:
var app = angular.module('app',[]);
app.controller('ParentController', function($scope, $get){
//get data from AJAX call
this.data = getDataFromAjaxCall();
}
Child1 Component:
app.component('child1',{
bindings : {
data : '='
},
controller: function($scope){
var self = this;
self.$onChanges = function(changes){
if(changes.data)
console.log('data changed');
}
self.addData = function(){
self.data.push({
id : 10,
name : 'abc'
});
}
}
});
Child2 Component:
app.component('child2',{
bindings : {
data : '='
},
controller: function($scope){
var self = this;
self.$onChanges = function(changes){
if(changes.data)
console.log('data changed');
}
self.addData = function(){
self.data.push({
id : 20,
name : 'pqr'
});
}
}
});
I expect to get the updated data in child1 component if the child2 component modified the data and vice versa.
The $onChanges life-cycle hook only trigger on changes to one-way ("<") and attribute ("#") bindings. It does not trigger on changes to two-way ("=") bindings.
With components, use one-way ("<") binding for inputs and expression ("&") binding for outputs:
app.component('child1',{
bindings: {
̶d̶a̶t̶a̶ ̶:̶ ̶'̶=̶'̶
facts: "<",
factsChange: "&",
},
controller: function(){
this.$onChanges = function(changes){
if(changes.facts)
console.log('facts changed');
}
}
});
Avoid using two-way ("=") bindings. They make migration to Angular 2+ more difficult.
For more information, see AngularJS Developer Guide - Component-Based Application Architecture.
Also be careful with bindings that start with data. Directive normalization will strip names that start with data-. See AngularJS Developer Guide - Directive Normalization.
Functions that do XHRs can't return data. They can only return promises from which data need to be extracted.
var app = angular.module('app',[]);
app.controller('ParentController', function($scope, $get){
//get data from AJAX call
̶t̶h̶i̶s̶.̶d̶a̶t̶a̶ ̶=̶ ̶g̶e̶t̶D̶a̶t̶a̶F̶r̶o̶m̶A̶j̶a̶x̶C̶a̶l̶l̶(̶)̶;̶
var promise = getDataFromAjaxCall();
promise.then( response => {
this.data = response.data;
});
}
JavaScript browsers uses a single-threaded non-blocking event-driven architecture for IO. Programmers familiar with imperative programming styles need to change the way they think about IO with JavaScript browsers.

When to use the AngularJS `$onInit` Life-Cycle Hook

With the release of AngularJS V1.7, the option to pre-assign bindings to has deprecated and removed:
Due to 38f8c9, directive bindings are no longer available in the constructor.
To migrate your code:
If you specified $compileProvider.preAssignBindingsEnabled(true) you need to first migrate your code so that the flag can be flipped to false. The instructions on how to do that are available in the "Migrating from 1.5 to 1.6" guide. Afterwards, remove the $compileProvider.preAssignBindingsEnabled(true) statement.
— AngularJS Developer Guide - Migrating to V1.7 - Compile
Due to bcd0d4, pre-assigning bindings on controller instances is disabled by default. We strongly recommend migrating your applications to not rely on it as soon as possible.
Initialization logic that relies on bindings being present should be put in the controller's $onInit() method, which is guaranteed to always be called after the bindings have been assigned.
— AngularJS Developer Guide - Migrating from v1.5 to v1.6 - $compile
What are the use cases when code has to be moved to the $onInit Life-Cycle Hook? When can we just leave the code in the controller construction function?
Code has to be moved in the $onInit function, when it depends on bindings, because these bindings are not available within this in the constructor. They get assigned AFTER instantiation of the component class.
Example:
You have a state definition like this:
$stateProvider.state("app", {
url: "/",
views: {
"indexView": {
component: "category"
}
},
resolve: {
myResolve: (someService) => {
return someService.getData();
}
}
});
You can bind the result of myResolve to your component like this:
export const CategoryComponent = {
bindings: {
myResolve: "<"
},
controller: Category
};
If you now log out this.myResolve in the constructor and in $onInit you will see something like this:
constructor() {
console.log(this.myResolve); // <-- undefined
}
$onInit() {
console.log(this.myResolve); // <-- result of your resolve
}
So, your constructor should only contain constructing code like:
constructor() {
this.myArray = [];
this.myString = "";
}
Every angular specific initialisation and binding or dependency usage should be in $onInit

AngularJS anonymous component replace inner scope

Hi I am using Angular with ES6, now I want to get rid of the $scope since Angular2 will not use it anymore and I want to create futureprove code,... this works:
let tab = this.tabManager.getArea.open({
controller: ['$scope', function (scope) {
console.log(scope);
scope.close = function () {
tab.close();
}
}],
template: '<component-name on-close="close()" ...></component-name>'
});
but how do I rewrite it without injecting the scope, I thought about something like this:
let tab = this.tabManager.getArea.open({
controller: [class {
constructor() {
console.log('construct');
}
close() {
console.log('close');
tab.close();
}
}],
template: '<component-name on-close="close()" ...></component-name>'
});
But it does not seem to work properly, the construtor is called up, however the binding for on-close does not seem to work.
the controller class in 1.6 exposes $ctrl object , can access "close()" using that.try once

How to resolve Angular Service directly from an Angular Component

I am currently using Angular 1.5. I am using ui-router as my primary navigation mechanism. I am leveraging Angular components.
I understand that I can use .resolve on my states to instantiate services which are then passed down through my component hierarchy (mostly using one-way bindings).
One of my components is called literatureList and is used in more than one route/state. The literatureList component makes use of a specific service called literatureListService. literatureListService is only used by literatureList. literatureListService takes a while to instantiate, and uses promises etc.
In each of the .state definitions then I need to have a .resolve that instantiates literatureListService. This means that I need to refer to this literatureListService in each of the .state.resolve objects. This doesn't seem very DRY to me.
What I'd really like to do is remove the literatureListService references from the .state.resolve objects and 'resolve' the service from 'within' the literatureList component itself.
How do I code a 'resolve-style' mechanism within the literatureList component that will handle the async/promise nature of literatureListService? What is best practice for doing this?
Code snippets follow:
state snippets:
$stateProvider.state({
name: 'oxygen',
url: '/oxygen',
views: {
'spofroot': { template: '<oxygen booklist="$resolve.literatureListSvc"></oxygen>' }
},
resolve:{
literatureListSvc: function(literatureListService){
return literatureListService.getLiterature();
}
}
});
$stateProvider.state({
name: 'radium',
url: '/radium',
views: {
'spofroot': { template: '<radium booklist="$resolve.literatureListSvc"></radium>' }
},
resolve:{
literatureListSvc: function(literatureListService){
return literatureListService.getLiterature();
}
}
});
literatureListService:
angular.module('literature')
.factory('literatureListService',function($http,modelService){
// Remember that a factory returns an object, whereas a service is a constructor function that will be called with 'new'. See this for a discussion on the difference: http://blog.thoughtram.io/angular/2015/07/07/service-vs-factory-once-and-for-all.html
console.log('literatureListService factory is instantiating - this will only happen once for each full-page refresh');
// This is a factory, and therefore needs to return an object containing all the properties that we want to make available
var returnObject = {}; // Because this is a factory we will need to return a fully-formed object (if it was a service we would simply set properties on 'this' because the 'context' for the function would already have been set to an empty object
console.log('service instantiation reporting that modelManager.isDataDirty='+modelService.isDataDirty);
// The getLiterature method returns a promise, and therefore can only be consumed via a promise-based mechanism
returnObject.getLiterature = function($stateParams){
console.log('literatureService.getLiterature will now return a promise (via a call to $http)');
return $http({method: 'GET', url: 'http://localhost:3000/literature/'});
};
return returnObject;
});
oxygen component html:
<div>
This is the OXYGEN component which will now render a literature list, passing in bookList.data as books
<literature-list books="$ctrl.booklist.data"></literature-list>
</div>
oxygen component js
angular.module('frameworks')
.component('oxygen',{
templateUrl:"frontend/framework/frameworks/oxygenComponent.html",
controller:function($http){
var $ctrl = this;
console.log('Hello from the oxygen component controller with literatureListSvc='+$ctrl.booklist); // Bound objects NOT YET AVAILABLE!!!!!!
this.$onInit = function() {
//REMEMBER!!!! - the bound objects being passed into this component/controller are NOT available until just before the $onInit event fires
console.log('Hello from the oxygen component controller onInit event with bookList='+JSON.stringify($ctrl.booklist));
};
}
,bindings:{ // remember to make these lowercase!!!
booklist:'<'
}
});
literatureList component html:
<div>
{{$ctrl.narrative}}
<literature-line ng-repeat="literatureItem in $ctrl.books" wizard="fifteen" book="literatureItem" on-tut="$ctrl.updateItemViaParent(itm)">555 Repeat info={{literatureItem.title}}</literature-line>
</div>
literatureList component js
angular.module('literature')
.component('literatureList',{
templateUrl:'frontend/literature/literatureListComponent.html',
//template:'<br/>Template here33 {{$ctrl.listLocalV}} wtfff',
// controller:function(literatureListService){
controller:function(){//literatureListService){
var $ctrl=this;
this.narrative = "Narrative will unfold here";
this.updateItemViaParent = function(book){
this.narrative = 'just got notified of change to book:'+JSON.stringify(book);
};
this.$onInit = function(){
console.log('literatureList controller $onInit firing with books='+JSON.stringify($ctrl.books));
};
this.$onChanges = function(){
console.log('literatureList controller $onChanges firing');
};
},
bindings: {
books:'<'
}
});
As JavaScript in reference based, you can crete object in your service and access it in all three controllers that you need.
For Example:
function serviceA() {
var vm = this;
vm.testObject = {};
vm.promise1().then(function(response) {
vm.testObject = response;
})
}
function ControllerA($scope, serviceA) {
$scope.testA = service.testObject;
}
In this case, as soon as the promise is resolved, all the controllers will get the value of the response and can be used in the partials respecively

UI-Router | Inject a service(or the resolved object) in the template function of the nested views

I have a scenario where I want to render the template only when some condition is met(on the basis of data we get from the REST call). So, I did the following :
My Code (by injecting the factory into the template function):
.state('dashboard.deal.xyz', {
url: "/xyz/:dealId",
resolve: {
permissions: function(dealUnit, $state) {
console.log('Inside the resolve of permissions :::', $state.params.dealId);
return dealUnit.getUnitPermissions('123412341234DealId');
}
},
views: {
"viewA": {
template: function(dealUnit) {
//Here I want to inject either 'permissions' (the resolve object)
//or the 'dealUnit' factory which gets me the permissions from the server directly.
return '<h1>return the template after getting the permissions resolve object</h1>';
}
},
"viewB": {
template: "viewB"
}
}
})
My 'dealUnit' factory is working fine and returns an object when I use it inside the 'resolve' of the state. But, that factory is not being injected when I inject it inside the template function of the nested view.
Am I doing it correctly? And if not then how should I go about doing it ?
In case, we want to do some "magic" before returning the template... we should use templateProvider. Check this Q & A:
Trying to Dynamically set a templateUrl in controller based on constant
Because template:... could be either string or function like this (check the doc:)
$stateProvider
template
html template as a string or a function that returns an html template as a string which should be used by the uiView directives. This property takes precedence over templateUrl.
If template is a function, it will be called with the following parameters:
{array.} - state parameters extracted from the current $location.path() by applying the current state
template: function(params) {
return "<h1>generated template</h1>"; }
While with templateProvider we can get anything injected e.g. the great improvement in angular $templateRequest. Check this answer and its plunker
templateProvider: function(CONFIG, $templateRequest) {
console.log('in templateUrl ' + CONFIG.codeCampType);
var templateName = 'index5templateB.html';
if (CONFIG.codeCampType === "svcc") {
templateName = 'index5templateA.html';
}
return $templateRequest(templateName);
},

Resources