AngularJS component bindings not passing object - angularjs

Receiving error cannot read property 'type' of undefined at PollListCtrl.runQuery.
I'm not sure why this is coming up undefined. I've console logged profile-polls.controller.js where listConfig is created to make sure there is an object here. The response is
{type: "all", filters: {pollsCreated: Array(3)}}
But it is coming up undefined when I try to pass it to poll-list component in the profile-polls.html. Below is the gist for the relevant files. Can anyone tell me why this is not passing correctly?
https://gist.github.com/RawleJuglal/fa668a60e88b6f7a95b456858cf20597

I think you need to define watcher for your component. On start listConfig is undefined and only after some delay (next digest cycle) it gets value. So we create watcher and cancel it after if statement.
app.component('pollList', {
bindings: {
listConfig: '='
},
templateUrl: 'poll-list.html',
controllerAs: 'vm',
controller: PollListCtrl
});
function PollListCtrl($scope) {
var vm = this;
function run(){
console.log(vm.listConfig);
}
var cancelWatch = $scope.$watch(function () {
return vm.listConfig;
},
function (newVal, oldVal) {
if (newVal !== undefined){
run();
cancelWatch();
}
});
}
Demo plunkr

You should never use $scope in your angularjs application.
You can use ngOnInit() life cycle hook to access "bindings" value instead constructor.
ngOnInit(){
$ctrl.listConfig = { type: 'all' };
$ctrl.limit = 5;
}

Related

ui-router 1.x.x change $transition$.params() during resolve

Trying to migrate an angularjs application to use the new version of angular-ui-router 1.0.14 and stumbled upon a problem when trying to change $stateParams in the resolve of a state.
For example, previously (when using angular-ui-router 0.3.2) modifying $stateParams worked like this:
$stateProvider.state('myState', {
parent: 'baseState',
url: '/calendar?firstAvailableDate',
template: 'calendar.html',
controller: 'CalendarController',
controllerAs: 'calendarCtrl',
resolve: {
availableDates: ['CalendarService', '$stateParams', function(CalendarService, $stateParams) {
return CalendarService.getAvailableDates().then(function(response){
$stateParams.firstAvailableDate = response[0];
return response;
});
}]
}
})
The problem is firstAvailableDate is populated after a resolve and I do not know how to update $transition$.params() during a resolve when usign the new version of angular-ui-router 1.0.14.
I have tried, and managed to update the url parameter with
firing a $state.go('myState', {firstAvailableDate : response[0]}) but this reloads the state, so the screen flickers
modified $transition$.treeChanges().to[$transition$.treeChanges().length-1].paramValues.firstAvailableDate = response[0]; to actually override the parameters. I have done this after looking through the implementation on params() for $transition$.
Although both those options work, they seem to be hacks rather than by the book implementations.
What is the correct approach to use when trying to modify parameters inside a resolve?
Approach with dynamic parameter:
Take a look at this document: params.paramdeclaration#dynamic. Maybe thats what you are looking for: ...a transition still occurs....
When dynamic is true, changes to the parameter value will not cause the state to be entered/exited. The resolves will not be re-fetched, nor will views be reloaded.
Normally, if a parameter value changes, the state which declared that the parameter will be reloaded (entered/exited). When a parameter is dynamic, a transition still occurs, but it does not cause the state to exit/enter.
This can be useful to build UI where the component updates itself when the param values change. A common scenario where this is useful is searching/paging/sorting.
Note that you are not be able to put such logic into your resolve inside your $stateProvider.state. I would do this by using dynamic parameters to prevent the state reload. Unfortunally, the dynamic rules doesn't work when you try to update your state (e.g. by using $stage.go()) inside the resolve part. So I moved that logic into the controller to make it work nice - DEMO PLNKR.
Since userId is a dynamic param the view does not get entered/exited again when it was changed.
Define your dynamic param:
$stateProvider.state('userlist.detail', {
url: '/:userId',
controller: 'userDetail',
controllerAs: '$ctrl',
params: {
userId: {
value: '',
dynamic: true
}
},
template: `
<h3>User {{ $ctrl.user.id }}</h3>
<h2>{{ $ctrl.user.name }} {{ !$ctrl.user.active ? "(Deactivated)" : "" }}</h2>
<table>
<tr><td>Address</td><td>{{ $ctrl.user.address }}</td></tr>
<tr><td>Phone</td><td>{{ $ctrl.user.phone }}</td></tr>
<tr><td>Email</td><td>{{ $ctrl.user.email }}</td></tr>
<tr><td>Company</td><td>{{ $ctrl.user.company }}</td></tr>
<tr><td>Age</td><td>{{ $ctrl.user.age }}</td></tr>
</table>
`
});
Your controller:
app.controller('userDetail', function ($transition$, $state, UserService, users) {
let $ctrl = this;
this.uiOnParamsChanged = (newParams) => {
console.log(newParams);
if (newParams.userId !== '') {
$ctrl.user = users.find(user => user.id == newParams.userId);
}
};
this.$onInit = function () {
console.log($transition$.params());
if ($transition$.params().userId === '') {
UserService.list().then(function (result) {
$state.go('userlist.detail', {userId: result[0].id});
});
}
}
});
Handle new params by using $transition.on* hooks on route change start:
An other approach would be to setup the right state param before you change into your state. But you already said, this is something you don't want. If I would face the same problem: I would try to setup the right state param before changing the view.
app.run(function (
$transitions,
$state,
CalendarService
) {
$transitions.onStart({}, function(transition) {
if (transition.to().name === 'mySate' && transition.params().firstAvailableDate === '') {
// please check this, I don't know if a "abort" is necessary
transition.abort();
return CalendarService.getAvailableDates().then(function(response){
// Since firstAvailableDate is dynamic
// it should be handled as descript in the documents.
return $state.target('mySate', {firstAvailableDate : response[0]});
});
}
});
});
Handle new params by using $transition.on* hooks on route change start via redirectTo
Note: redirectTo is processed as an onStart hook, before LAZY resolves.
This does the same thing as provided above near the headline "Handle new params by using $transition.on* hooks on route change start" since redirectTo is also a onStart hook with automated handling.
$stateProvider.state('myState', {
parent: 'baseState',
url: '/calendar?firstAvailableDate',
template: 'calendar.html',
controller: 'CalendarController',
controllerAs: 'calendarCtrl',
redirectTo: (trans) => {
if (trans.params().firstAvailableDate === '') {
var CalendarService = trans.injector().get('CalendarService');
return CalendarService.getAvailableDates().then(function(response){
return { state: 'myState', params: { firstAvailableDate: response[0] }};
});
}
}
});

Implementing component require property in Angular 1.5 components

I am having no joy with implementing require: {} property as part of an angular component. Allow me to demonstrate with an example I have.
This is the component/directive that supposed to fetch a list of judgements. Nothing very fancy, just a simple factory call.
// judgements.component.js
function JudgementsController(GetJudgements) {
var ctrl = this;
ctrl.Get = function () {
GetJudgements.get().$promise.then(
function (data) {
ctrl.Judgements = data.judgements;
}, function (error) {
// show error message
});
}
ctrl.$onInit = function () {
ctrl.Get();
};
}
angular
.module('App')
//.component('cJudgements', {
// controller: JudgementsController,
//});
.directive('cJudgements', function () {
return {
scope: true,
controller: 'JudgementsController',
//bindToController: true,
};
});
I am trying to implement component require property to give me access to ctrl.Judgements from the above component/directive as follows:
// list.component.js
function ListController(GetList, GetJudgements) {
var ctrl = this;
ctrl.list = [];
ctrl.Get = function () {
GetList.get().$promise.then(
function (data) {
ctrl.list = data.list;
}, function (error) {
// show error message
});
};
//ctrl.GetJudgements = function () {
// GetJudgements.get().$promise.then(
// function (data) {
// ctrl.Judgements = data.judgements;
// }, function (error) {
// // show error message
// });
//}
ctrl.$onInit = function () {
ctrl.Get();
//ctrl.GetJudgements();
};
}
angular
.module('App')
.component('cTheList', {
bindings: {
listid: '<',
},
controller: ListController,
controllerAs: 'ctrl',
require: {
jCtrl: 'cJudgements',
},
template: `
<c-list-item ng-repeat="item in ctrl.list"
item="item"
judgements="ctrl.Judgements"></c-list-item>
<!--
obviously the reference to judgements here needs to change
or even better to be moved into require of cListItem component
-->
`,
});
Nice and simple no magic involved. A keen reader probably noticed GetJudgement service call in the ListController. This is what I am trying to remove from TheList component using require property.
The reason? Is actually simple. I want to stop database being hammered by Judgement requests as much as possible. It's a static list and there is really no need to request it more than once per instance of the app.
So far I have only been successful with receiving the following error message:
Error: $compile:ctreq
Missing Required Controller
Controller 'cJudgements', required by directive 'cTheList', can't be found!
Can anyone see what I am doing wrong?
PS: I am using angular 1.5
PSS: I do not mind which way cJudgement is implemented (directive or component).
PSSS: If someone wonders I have tried using jCtrl: '^cJudgements'.
PSSSS: And multiple ^s for that matter just in case.
Edit
#Kindzoku posted a link to the article that I have read before posting the question. I hope this also helps someone in understanding $onInit and require in Angular 1.5+.
Plunker
Due to popular demand I made a plunker example.
You should use required components in this.$onInit = function(){}
Here is a good article https://toddmotto.com/on-init-require-object-syntax-angular-component/
The $onInit in your case should be written like this:
ctrl.$onInit = function () {
ctrl.jCtrl.Get();
};
#iiminov has the right answer. No parent HTML c-judgements was defined.
Working plunker.

Angular JS - Update Scope value on click

Right now I am passing my parameter through the state like:
.state('app.listing', {
url: '/ad/listing/:adId/{type}',
params: {
adId: {
value: "adId",
squash: false
}, type: {
value: null,
squash: true
}
},
This works as I can get "type" from $stateParams and update my get request.
Is there not a way to do this from a click event and not use $stateParams for passing the "type" param?
I basically have a button that filters results and passes the type param from the button. It would be a lot easier if I can just attach a click event to it which then updates my get request.
Just messing around I tried doing something like
$scope.filter = function(type) {
if(type) {
return type;
}
return '' ;
}
$scope.type = $scope.filter();
Service is like
$http.get(API_ENDPOINT.url + '/listing/' + adId, {
params: {
page: page,
type: type // essentially $scope.type
},
}).
and then on my button I have
<button ng-click="filter('2')"></button>
^ This will pass 2 for type, but won't reinit the http get call on click. Do I need to broadcast the change is there a simple way to do this?
Does this even make sense? The code above is just mock to give an idea, but open to suggestions if any.
Angular never requires you to make broadcasts to reflect changes made to scopevariables via the controller
var typeWatcher = '1';
$scope.filter = function(type){
if (type !== typeWatch)
{
$http.get(API_ENDPOINT.url + '/listing/' + adId, {
params: {
page: page,
type: type // essentially $scope.type
},
});
typeWatcher = type;
}
};
You can wrap your get call in a function & call it after the filter function in ng-click
$scope.functionName = function () {
return $http.get(API_ENDPOINT.url + '/listing/' + adId, {
params: {
page: page,
type: type // essentially $scope.type
}
})
}
then in HTML
<button ng-click="filter('2'); functionName()"></button>
Well, To call $http.get method on click,
$scope.filter = function(type) {
if(type) {
//call the method using service.methodName
return type;
}
return '' ;
}
and wrap that $http.get method to one function.
Hope it helps you.
Cheers

Update property of object in callback function of Angular component

I want to create a new Angular compontent, which should change the status of an object/model and then call the defined callback function.
The code looks like this:
HTML
<status-circle status="obj.status" on-update="save(obj)"></status-circle>
statusCirlcle.js
angular.module('app').component('statusCircle', {
templateUrl: '/views/components/statusCircle.html',
bindings: {
status: '=',
onUpdate: '&'
},
controller: function () {
this.update = function (status) {
if(this.status !== status) {
this.status = status;
this.onUpdate();
}
};
}
});
The property will be updated correctly (two-way binding works) and the save callback function gets called, BUT the object has the old status property set.
My question: How can I update the object in the callback function?
JSFiddle: https://jsfiddle.net/h26qv49j/2/
It looks like in statusCircle.js you should change
this.onUpdate();
to
this.onUpdate({status: status});
and then in the HTML change
<status-circle status="obj.status" on-update="save(obj)"></status-circle>
To
<status-circle status="obj.status" on-update="save(status)"></status-circle>
And lastly... in your controller where your save() function is put
this.save = function(status) {this.obj.status = status};

How to call refresh() on a kendo-grid from an Angular controller?

I'm attempting to follow several suggestions on refreshing a kendo-grid such as this.
The essential is that in the html I have:
<div kendo-grid="vm.webapiGrid" options="vm.mainGridOptions">
Then in the controller I have:
vm.webapiGrid.refresh();
Note: I'm using the ControllerAs syntax so I am using "vm" rather than $scope.
My problem is that "vm.webapiGrid" is undefined. This seems so straightforward, but I'm not sure why it is undefined.
Found the answer. One other method of refreshing the datasource I read about was to do something like:
vm.mainGridOptions.datasource.transport.read();
This wasn't working for me as "read" was undefined. Looking at my datasource definition, I saw the reason, read needs a parameter (in this case "e"):
vm.mainGridOptions = {
dataSource: {
transport: {
read: function (e) {
task.getAllTasks(vm.appContext.current.contextSetting).
then(function (data) {
e.success(data);
});
},
}
},
To solve, I saved "e" in my scope and then reused it when I wanted to refresh:
vm.mainGridOptions = {
dataSource: {
transport: {
read: function (e) {
task.getAllTasks(vm.appContext.current.contextSetting).
then(function (data) {
e.success(data);
vm.optionCallback = e;
});
},
}
},
and then:
if (vm.optionCallback !== undefined) {
vm.mainGridOptions.dataSource.transport.read(vm.optionCallback);
}
Problem solved (I hope).
it's because you are using the options object to trigger the read, you should use the grid reference instead:
<div kendo-grid="vm.webapiGrid" options="vm.mainGridOptions">
as in:
$scope.vm.webapiGrid.dataSource.transport.read();
hope that helps.
Add id to the grid and trying refreshing using it.
<div kendo-grid="vm.webapiGrid" options="vm.mainGridOptions" id="grid1">
In controller use this:
$("#grid1").data('kendoGrid').refresh();

Resources