Access fileitem from child controller on parent controller in AngularJS - angularjs

I am using angular file uploader in child component and need to access the fileitem when onAfterAddingFile is fired. I have implemented binding in component. So far, I have tried this-
Childcontroller:
$onInit() {
this.feedFileInfo = 'abc';//here it is updated
this.uploader.filters.push({
name: 'customFilter',
fn: function(item /*{File|FileLikeObject}*/, options) {
return this.queue.length < 10;
}
});
this.uploader.onAfterAddingFile = function(fileItem) {
console.log('fileItem');
this.feedFileInfo = 'xyz';//this value is not being updated to feedFileInfo variable and hence cannot be obtained in parent controller
console.info('onAfterAddingFile', fileItem);
};
I need the updated value ie. fileitem in this variable.
Any guidance would be appreciated.

You use the this from the declared function, not the outer one.
One classical workaround is to use:
var that = this;
this.uploader.onAfterAddingFile = function (fileItem) {
that.feedFileInfo = 'xyz';
};

Related

How to update child components from the updated list of parents

I'm new to Angular and currently using version 1.6.
I'm implementing the component style of Angular. I just want to ask what's the best way to communicate from parent to child components? I know there is an existing question but I have a specific scenario (I'm not sure if it's unique or not).
Here is the scenario:
Modal -> create new todo -> Parent ( update the object ) -> personal todo ( update the list )
I have a modal for creating todo.
Then after creating new todo pass the value on the parent to update the object of todo.
And when I updated the parent list of todo pass to the personal todo components to update the list on the view.
angular.module('tab')
.controller('TabController', TabController);
function TabController() {
let vm = this;
let updatedTodoObject = {};
vm.$onInit = function () {
vm.personalTodo = vm.todo.own_todo;
vm.externalTodo = vm.todo.external_todo;
}
vm.$onChanges = function (changes) {
console.log('I\'m triggered');
}
vm.updateTodoList = updateTodoList;
function updateTodoList( result ) {
updatedTodoObject = angular.copy(vm.todo);
updatedProjectObject.user_todos.push(result)
if( vm.todo !== updatedTodoObject) {
vm.todo = updatedTodoObject;
} else {
console.log("Still in reference");
}
}
vm.getUpdatedTodotList = function( ) {
return vm.todo;
}
}
angular.module('...')
.component('...', {
bindings: {
onResultTodoUpdated: '&'
},
controllerAs: 'todo',
controller: ['TodoService', '$log', '$state', function(TodoService, $log, $state) {
let vm = this;
let todo = {};
vm.newTodoModal = function() {
TodoService.newTodoModal()
.then(function (TodoName) {
TodoService.createTodo(TodoName)
.then(function(response) {
if( response.status === 201 ) {
todo = {
...
...
}
vm.onResultTodoUpdated( { result: todo } );
}
})
.catch(function(error) {
console.log(error);
});
angular.module('...')
.component('...', {
bindings: {
todos: "<"
},
controllerAs: 'personal',
controller: function(){
let vm = this;
vm.isShowTodoArchived = false;
vm.$onInit = function () {
getWatchedTodo();
}
function getWatchedTodo () {
vm.todos = vm.todos;
vm.todosSize = vm.todos.length;
}
My question again is how I can pass the updated data after I create to the child component which is in charge of displaying the todo list?
UPDATED
<div class="tab-pane active" id="todosTab">
<nv-new-todo on-result-todo-updated="todo.updateTodoList(result)"></nv-new-project>
<div class="my-todos">
<nv-personal-todo todos="todo.personalTodo" ></nv-personal-todo>
<nv-external-todo todos="todo.externalTodo"></nv-external-todo>
</div>
</div>
How to update child components with changes from parents
Use one-way bindings <
< or <attr - set up a one-way (one-directional) binding between a local scope property and an expression passed via the attribute attr. The expression is evaluated in the context of the parent scope. If no attr name is specified then the attribute name is assumed to be the same as the local name. You can also make the binding optional by adding ?: <? or <?attr.
For example, given <my-component my-attr="parentModel"> and directive definition of scope: { localModel:'<myAttr' }, then the isolated scope property localModel will reflect the value of parentModel on the parent scope. Any changes to parentModel will be reflected in localModel, but changes in localModel will not reflect in parentModel.
— AngularJS Comprehensive Directive API Reference - scope
And the $onChanges life-cycle hook:
$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 { currentValue, previousValue, isFirstChange() }. Use this hook to trigger updates within a component.
— AngularJS Developer Guide - Components
With object content — Use the $doCheck Life-cycle Hook
When binding an object or array reference, the $onChanges hook only executes when the value of the reference changes. To check for changes to the contents of the object or array, use the $doCheck life-cycle hook:
app.component('nvPersonalTodo', {
bindings: {
todos: "<"
},
controller: function(){
var vm = this;
this.$doCheck = function () {
var oldTodos;
if (!angular.equals(oldTodos, vm.todos)) {
oldTodos = angular.copy(vm.todos);
console.log("new content");
//more code here
};
}
})
From the Docs:
The controller can provide the following methods that act as life-cycle hooks:
$doCheck() - Called on each turn of the digest cycle. Provides an opportunity to detect and act on changes. Any actions that you wish to take in response to the changes that you detect must be invoked from this hook; implementing this has no effect on when $onChanges is called. For example, this hook could be useful if you wish to perform a deep equality check, or to check a Date object, changes to which would not be detected by Angular's change detector and thus not trigger $onChanges. This hook is invoked with no arguments; if detecting changes, you must store the previous value(s) for comparison to the current values.
— AngularJS Comprehensive Directive API Reference -- Life-cycle hooks
For more information,
AngularJS angular.equals API Reference
AngularJs 1.5 - Component does not support Watchers, what is the work around?
Simple DEMO
angular.module("app",[])
.component("parentComponent", {
template: `
<fieldset>
Inside parent component<br>
parentData={{$ctrl.parentData}}
<child-component in-data="$ctrl.parentData"></child-component>
</fieldset>
`,
controller: function () {
this.$onInit = () => {
this.parentData = 'test'
};
},
})
.component("childComponent",{
bindings: {
inData: '<',
},
template: `
<fieldset>Inside child component<br>
inData={{$ctrl.inData}}
</fieldset>
`,
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app">
<parent-component>
</parent-component>
<body>
For more information, see
AngularJS Developer Guide - Component-based application architecture
AngularJS Comprehensive API Reference - scope

In Angular 1.5, how to compile the html of a parent component from a child component?

I have two angular components: app-menuitem and app-menu. app-menu has a list of app-menuitem as children but there is no transclude.
App-menuitem
angular.module('app')
.component('appMenuitem', {
transclude: false,
controller: menuitemController,
require: {
parent: '^?app-menu'
},
bindings: {
...
groupradio: '#',
isactive: '<', // bind to active (just init)
...
},
templateUrl: 'angular/components/simple/menuitem/menuitem.html'
});
function menuitemController($rootScope, $scope, $element, $attrs) {
var ctrl = this;
//Default values
ctrl.$onInit = function () {
if(ctrl.isactive){
ctrl.active = true;
}else{
ctrl.active = false;
}
ctrl.selectRadioItem = function(){
if(!ctrl.active){
var currentMenu = this.parent.items.menu;
var levelMenu = this.parent.items.level;
for(var i = 0; i < currentMenu.length; i++){
var currentMenuItem = currentMenu[i];
if(currentMenuItem.groupradio === ctrl.groupradio){
if(currentMenuItem.index === ctrl.index){
currentMenuItem.isactive = true;
}else{
currentMenuItem.isactive = false;
}
currentMenu[i] = currentMenuItem;
}
}
this.parent.items.menu = currentMenu;
console.dir(this.parent); //<-- updates are visible but the html did not change.
}
...
As you can see at the end of this code, I managed to modify the parent component app-menu from this child component app-menuitem, but the HTML is never compiled again in this case. Someone has an idea ?
I suggest not to change values of parent directly from children. Instead, expose a method on the parent's controller that is invoked from the child with the needed update and let the parent handle the updates.
This allows you both to avoid more costly bindings as well as keep the control of a controller's properties in the controller itself (hence allowing you to more easily find the error source in your code). It is also more testable, if you are testing your code.
Small Tip: For test purposes, if something doesn't update after you
update the model, you could always try to do $scope.$apply() after the
update and see if there's a $digest timing issue. Do not use in
production unless you have to - it is costly and can easily cause run
time exceptions

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 mock variable in directive unit test?

I have created a custom directive and would like to mock out a variable to make the test working. This is part of the unit test:
it('should pass on initialvalue', function() {
scope.$apply(function() {
scope.initial = 2;
scope.Repairer = null;
});
expect(elementScope.initial).toEqual(2);
});
The directive is calls a service when the initial-variable is set. Both tests are failing because in the directive I have a variable called request that is not properly mocked. The question is how can I mock this out? Or do I need to put the request variable on scope? This is part of the code:
if (scope.Repairer) {
console.log('calling scriptservice');
var request = {
allocationProcess: (scope.$parent.lodgement.searchSettings.automatic.visible) ? 'automatic' : 'direct',
allocationSource: 'internal',
brand: brandScriptTag, // lookup brand scripting name
includeSegment: false,
relationship: scope.repairer.relationshipCode,
ruralSearch: scope.repairer.isRural,
state: scope.$parent.lodgement.claimLocation
};
scriptingService.getScript(request).then(function (scripts) {
scope.scripts = scripts;
});
} else {
scope.scripts = null;
}
plunker ref:http://plnkr.co/edit/juDwpot727jzxzzWeaJl?p=preview
You are accessing an object on the $parent scope, so I'de do somthing like:
$rootScope.lodgement = jasmine.any(Object);
However, your code is problematic since it's asuming a lot about this lodgement...
//it's better to use jasmine.any(Object)
//$rootScope.lodgement = jasmine.any(Object);
var lodgement_mock = {searchSettings:{automatic:{visible:false}}};
$rootScope.lodgement = lodgement_mock;
p.s.
you had another error in your directive - you tried accessing scope.repairer instead of scope.Repairer
check out this:
http://plnkr.co/edit/OFTZff53BXGNLQqRfE8L?p=preview

State parameters outside of the url?

Is there a way to pass parameters to a state without them being represented in the url?
Let's say I have the following state:
.state('accounts.content.viewaccounts.details.contacts.edit', {
url:'/edit/{contactId:[0-9]{1,10}}',
templateUrl: 'app/common/templates/StandardForm.html',
resolve: {blahblah}
Is there a way for me to send an optional parameter to this state, when navigating to it with $state.go, without adding it into the url?
You can use shared properties service and pass elements between controllers:
.service('sharedProperties', function () {
var item = null;
return {
getItem: function () {
return item;
},
setItem: function(value) {
item = value;
}
}});
Then before you move to controller you use:
sharedProperties.setItem(your-item);
and after you load the new controller you use:
var model = sharedProperties.getItem();
No. Not without using something like a service. see #Liad's answer

Resources