I have a component that looks like this:
var meadow = angular.module('meadow');
meadow.component('productIncome', {
bindings: {
product: '='
},
templateUrl: '/static/js/app/product-detail/product-income/product-income.html',
controller: function () {
var vm = this;
console.log(vm.product)
}
})
Now I want to make us of the product that's bound to the component, but it's telling me that "vm.product is undefined". I've tried using $onInit but it still produces the same results. For $onInit it did :
vm.$onInit = function(){
console.log(vm.product)
}
product-income.html
<product-income product="$ctrl.products.product"></product-income>
How can I get this working? Thanks
I made a working demo here:
var meadow = angular.module('meadow', []);
meadow = meadow.controller("abc", function($scope) {
$scope.products = {
product: "a"
}
});
meadow.component('productIncome', {
bindings: {
product: '='
},
template: "<div></div>",
controller: function() {
var vm = this;
vm.$onChanges = function() {
console.log("onchange", vm.product);
}
setTimeout(function() {
console.log("timeout", vm.product);
});
}
})
<script src="https://code.angularjs.org/1.6.3/angular.min.js"></script>
<div ng-app="meadow" ng-controller="abc">
<product-income product="products.product"></product-income>
</div>
So it seems that the order of the controllers are not sure, u should make a async to get the value, or use "$onChanges " to listen to the value.
Related
I have two directives in my code where parent directive's template contains child directive. The child directive is passed an attribute called "items" and it calls a function which will be returning a promise.
The child directive's controller then takes that items promise and does a 'then' to get the results. These results are then to be displayed in the dropdown.
I have been working with this code for a while now and am not able to figure out the warning I am getting. Also, I am not getting the right results.
Basically, when you click on Change Value button, it will return a different array. Odd numbers and event numbers provide different arrays (this was so I could simulate change to the model).
Below is the code and here is a link to the Codepen
Parent Controller
app.controller("mainCtrl", function($scope, $q) {
var that = this;
that.r = 0;
var i = [{ name: "toronto" }, { name: "chicago" }, { name: "new york" }];
var j = [{ name: "Martin" }, { name: "Stephanie" }, { name: "Kirk" }];
this.getItems = function () {
var deferred = $q.defer();
if (that.r % 2 == 0) {
console.log("getItems: i returned");
deferred.resolve(i);
} else {
console.log("getItems: j returned");
deferred.resolve(j);
}
return deferred.promise;
};
this.changeVal = function() {
that.r += 1;
console.log(that.r);
};
});
Parent Directive
app.directive("parentDirective", function() {
return {
template:
'<child-directive items="ct.getItems()"></child-directive><button ng-click="ct.changeVal()">Change Value</button>',
controller: "mainCtrl",
controllerAs: "ct",
scope: {}
};
});
Child Controller
app.controller("childCtrl", function($scope) {
$scope.name = "Child controller";
$scope.returnedItems =[];
$scope.items.then(function(result){
console.log("Child controller THEN from promise executed");
$scope.returnedItems = result;
});
});
Child Directive
app.directive("childDirective", function() {
return {
template:
'<select ng-model="selectedAction" ng-options="x.name for x in returnedItems"><option value="">Select</option></select>',
controller: "childCtrl",
scope: {
items: "<"
}
};
});
when I run the code above, I do get the drop-down populated. But it doesn't change after.
Appreciate any help on this. Thanks!
At the part in your child directive here,
items="ct.getItems()"
You are giving the child directive's scope the result of that function. In this case, a promise. When this promise gets resolved your child scope sets the ng-options to the result as expected. However subsequent calls to ct.getItems() creates a new promise that your child directive knows nothing about.
In my opinion, the cleanest way to get your results into the child is to pass the items array into the child directive. And whenever getItems() is called from the parent directive, update the results array. It simplifies a lot of the code. Here's your modified code. The main thing to note is that the child directive binds to currentItems array instead of the promise, and the array is updated whenever getItems() is called.
var app = angular.module("testApp", []);
app.controller("mainCtrl", function($scope, $q) {
var that = this;
var deferred = $q.defer();
that.r = 0;
var i = [{ name: "toronto" }, { name: "chicago" }, { name: "new york" }];
var j = [{ name: "Martin" }, { name: "Stephanie" }, { name: "Kirk" }];
that.currentItems = i;
this.getItems = function () {
if (that.r % 2 == 0) {
console.log("getItems: i returned");
that.currentItems = i;
deferred.resolve(i);
} else {
console.log("getItems: j returned");
that.currentItems = j;
deferred.resolve(j);
}
return deferred.promise;
};
this.changeVal = function() {
that.r += 1;
this.getItems();
console.log(that.r);
};
});
app.directive("parentDirective", function() {
return {
template:
'<child-directive items="ct.currentItems"></child-directive><button ng-click="ct.changeVal()">Change Value</button>',
controller: "mainCtrl",
controllerAs: "ct",
scope: {}
};
});
app.controller("childCtrl", function($scope) {
$scope.name = "Child controller";
});
app.directive("childDirective", function() {
return {
template:
'<select ng-model="selectedAction" ng-options="x.name for x in items"><option value="">Select</option></select>',
controller: "childCtrl",
scope: {
items: "<"
}
};
});
Heres a plunker using your example
I want to dynamically compile component for inserting this to specific DOM element (DOM also dynamically created by 3rd party library).
So, I use $compile, $scope.
https://jsbin.com/gutekat/edit?html,js,console,output
// ListController $postLink life cycle hook
function $postLink() {
...
$timeout(function () {
ctrl.items.splice(0, 1);
$log.debug('First item of array is removed');
$log.debug(ctrl.items);
}, 2000);
}
but below $onChanges life cycle hook in ListItemController isn't executed.
// ListItemController $onChanges life cycle hook
function $onChanges(changes) {
if (!changes.item.isFirstChange()) {
$log.debug(changes); // Not executed
}
}
I guess that angular.merge to pass item before ListItemController controller instance initialization is a major cause.
var itemScope = $scope.$new(true, $scope);
itemScope = angular.merge(itemScope, {
$ctrl: {
item: item
}
});
I modified you code a bit to demonstrate what is going on w/ the one way binding.
angular.module('app', [
'list.component',
'list-item.component'
]);
/**
* list.component
*/
angular
.module('list.component', [])
.component('list', {
controller: ListController,
template: '<div id="list"></div>'
});
ListController.$inject = ['$compile', '$document', '$log', '$scope', '$timeout'];
function ListController($compile, $document, $log, $scope, $timeout) {
var ctrl = this;
ctrl.$onInit = $onInit;
ctrl.$postLink = $postLink;
function $onInit() {
ctrl.items = [
{
id: 0,
value: 'a'
},
{
id: 1,
value: 'b'
},
{
id: 2,
value: 'c'
}
];
}
function $postLink() {
var index = 0;
// Not entirely sure what you need to do this. This can easily be done in the template.
/** ie:
* template: '<div id="list" ng-repeat="item in $ctrl.items"><list-item item="item"></list-item></div>'
**/
var iElements = ctrl.items.map(function (item) {
var template = '<list-item item="$ctrl.items[' + (index) + ']"></list-item>';
index++;
// you don't want to create an isolate scope here for the 1 way binding of the item.
return $compile(template)($scope.$new(false));
});
var listDOM = $document[0].getElementById('list');
var jqListDOM = angular.element(listDOM);
iElements.forEach(function (iElement) {
jqListDOM.append(iElement);
});
$timeout(function () {
// this will trigger $onChanges since this is a reference change
ctrl.items[0] = { id: 3, value: 'ss' };
// this however, will not trigger the $onChanges, if you need to use deep comparison, consider to use $watch
ctrl.items[1].value = 's';
ctrl.items[2].value = 's';
}, 2000);
}
}
/**
* list-item.component
*/
angular
.module('list-item.component', [])
.component('listItem', {
bindings: {
item: '<'
},
controller: ListItemController,
template: '<div class="listItem">{{ $ctrl.item.value }}</div>'
});
ListItemController.$inject = ['$log'];
function ListItemController($log) {
var ctrl = this;
ctrl.$onChanges = $onChanges;
function $onChanges(changes) {
if (!changes.item.isFirstChange()) {
$log.debug(changes); // Not executed
}
}
}
I'm trying to component-ise my codebase and have come up stuck on this problem.
When using $scopes and Controllers I would pass a server token to my rest call method with ng-init. Trying to do the same with a Component is not working.
javascript
angular
.module('myApp', [])
.controller('mainCtrl', function() {
var self = this;
self.options = function() {
var o = {}
o.token = self.serverToken
return o;
}
self.restData = {
url: 'http://rest.url',
options: self.options()
}
})
.component('myComponent', {
bindings: {
restData: '<'
},
template: '<p>template, calls child components</p>',
controller: function(restService) {
this.callRestService = function() {
restService.get(this.restData.url, this.restData.options)
}
console.log(this.restData.url) // http://rest.url
console.log(this.restData.options) // {token: undefined}
}
})
html
<html ng-app="myApp">
<!-- head -->
<body ng-controller="mainCtrl as m" ng-init="m.serverToken='12345'">
<my-component rest-data="m.restData"></my-component>
</body>
</html>
How do I pass the value to the component?
The issue is that the ng-init is executed after the controller is instantiated. However you are creating your restData object during the construction of your controller at which time the serverToken is undefined.
You could build your restData object after ng-init is called with something like this:
.controller('mainCtrl', function() {
var self = this;
self.restData = {};
self.init = function(token) {
self.serverToken=token;
self.restData = {
url: 'http://rest.url',
options: {token:token}
};
};
})
Your component can then do something when that restData changes. For example:
.component('myComponent', {
bindings: {
restData: '<'
},
template: '<p>template, calls child components</p>',
controller: function(restService) {
this.callRestService = function() {
restService.get(this.restData.url, this.restData.options)
}
this.$onChanges = function(changes) {
console.log(this.restData) // http://rest.url
console.log(this.restData.options) // {token: 12345}
this.callRestService();
}
}
});
The HTML would change to call your init method:
<body ng-controller="mainCtrl as m" ng-init="m.init(12345)">
<my-component rest-data="m.restData"></my-component>
</body>
What I am trying to do here is:
Type in the new language name and click "Add" button, the new language will be added into the existing object.
For example: the existing object: {"default": "English"}, When I type in "German", a new object is added like this: {"default": "English", "German": "German"}
Here is my PLUNKER.
Could someone help me on that? Thanks so much, I will appreciate!
I would prefer to use events. Just subscribe one piece on some event like:
$rootScope.$on('myEvent', function(event, info){
// do something
});
And another one will fire it:
scope.$broadcast('myEvent', info);
The system glitched when I was trying to save your plunkr or I don't have a permission so here the code:
var app = angular.module('plunker', ['ui.bootstrap']);
app.factory('Data', function(){
var data =
{
Language: ''
};
return {
setLanguage: function(language) {
data.Language = language;
}
}
})
var ModalDemoCtrl = function ($scope, $modal, $log) {
$scope.languages = {"sch": "Simple Chinese"};
$scope.$on('newLanguageAdded', function(e, lang){
$scope.languages[lang] = lang;
});
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl,
resolve: {
languages: function () {
return $scope.languages;
},
newLanguage: function () {
return $scope.newLanguage;
}
}
});
};
};
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
var ModalInstanceCtrl = function ($scope, $modal, $modalInstance, languages, newLanguage) {
$scope.languages = languages;
$scope.ok = function () {
$modalInstance.close($scope.languages);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
$scope.openDialog = function () {
var modalInstance = $modal.open({
templateUrl: 'addNewLanguageDialog.html',
controller: AddNewLanguageCtrl,
});
}
var AddNewLanguageCtrl = function ($scope, $rootScope, $modalInstance, Data){
$scope.newValue = {text: ''};
$scope.$watch('newLanguage', function(newValue) {
if(newValue) Data.setLanguage(newValue);
});
$scope.add = function () {
alert($scope.newValue.text);
$rootScope.$broadcast('newLanguageAdded', $scope.newValue.text);
$modalInstance.close($scope.languages);
}
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
}
}
};
You can just copy this piece into plunkr instead yours.
Also change the layout:
<div class="modal-body">
<input ng-model="newValue.text">
</div>
Let me know if something doesn't work
You need to use a service, by definition singletons, and inject it in both models, adding a watch to the array in the service and updating accordingly in the scope of every model, from the values in the service.
An angular-ui way to achieve what you need would be to use these two basic methodologies found in the angular-ui documentation. See associated plunker for the answer below.
First is to use the close(result) inside the Instance Controller of the modal which updates the result promise property of the Instance Controller
Second is to use the result promise property to get the result(s) passed on to the close() method
Inside The AddNewLanguageCtrl is something like this
$scope.data = {newLanguage: ""};
$scope.add = function() {
$scope.close(data.newLanguage);
};
Inside the your addNewLanguageDialog.html script template
instead of using
<input ng-model="newLanguage">
use this
<input ng-model="data.newLanguage">
This is because whenever a modal is created, a new scope is created under the $rootScope(default) if a scope is not passed on to the options when the $modal.open() is invoked as stated in the angular-ui documentation. If you use newLanguage as the model then it won't receive any updates inside the AddNewLanguageCtrl. You can read this to get a better understanding of what I'm talking about regarding scopes
Inside the first modal ModalInstanceCtrl is something like this
$scope.newLanguages = [];
$scope.openDialog = function () {
var modalInstance = $modal.open({
templateUrl: 'addNewLanguageDialog.html',
controller: AddNewLanguageCtrl,
});
modalInstance.result.then(function(newLanguage) {
if(newLanguage)
$scope.newLanguages.push(newLanguage);
});
};
And then in your ModalDemoCtrl
$scope.languages = [];
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'myModalContent.html',
controller: ModalInstanceCtrl
});
modalInstance.result.then(function(languages) {
$scope.languages = $scope.languages.concat(languages);
});
};
I am trying to reuse a few bigger functions over 3 controllers in Angular JS. I don't want to pin the functions to my root scope as I want to keep it clear of functions which will be used only 3 times within those 3 controllers.
angular.module('adminModule', ['adminDependency'])
.controller('ctrl1', ['$scope', 'details', function ($scope, details) {
// use functions
}])
.controller('ctrl2', ['$scope', 'details', function ($scope, details) {
// use functions
}])
.controller('ctrl3', ['$scope', 'details', function ($scope, details) {
// use functions
}])
Can you tell me how i can achieve that without writing my functions into the root scope?
Tried it inside a factory but calling AdminModule.toLevelKey() wont work...
.factory('AdminModule',
[ '$resource', 'serviceURL', function ($resource, serviceURL) {
return $resource(serviceURL + 'class/:id', {
id : '#id'
}, {
getClasses : {
method : 'GET',
url : serviceURL + 'extended/class',
isArray : true
},
toLevelKey : function (value) {
var return_key = parseInt(Math.floor(value / 3));
var return_level = value % 3;
return { level : return_level + 1, levelTranslationKey : return_key + 1 };
},
fromLevelKey : function (level, key) {
if (angular.isDefined(level)) {
var value = (key - 1) * 3 + (level - 1);
return value;
} else {
return null;
}
}
}
);
} ]);
This can be done by a service:
.service('myService', function(){
return {
fn: function(){
// do what you want
}
}
});
usage:
.controller('ctrl2', ['$scope', 'details', 'myService',
function ($scope, details, myService) {
// use functions
myService.fn();
}])
In accordance with the above comment of David FariƱa: "Are there even more options?".
Except executing, you also can pass data from one controller to another and broadcast event, when it happens.
SharedService:
angular.module("yourAppName", []).factory("mySharedService", function($rootScope){
var mySharedService = {};
mySharedService.values = {};
mySharedService.setValues = function(params){
mySharedService.values = params;
$rootScope.$broadcast('dataPassed');
}
return mySharedService;
});
FirstController:
function FirstCtrl($scope, mySharedService) {
$scope.passDataInSharedSevice = function(params){
mySharedService.setValues(params);
}
}
SecondController:
function SecondController($scope, mySharedService) {
$scope.$on('dataPassed', function () {
$scope.newItems = mySharedService.values;
});
}