Angular Nested Controller Data access - angularjs

I don't know how to access the data from a nested ( child ) controller.
<form ng-controller="TestController as test" ng-submit="submit()">
<h5>Employee name :</h5>
<div ng-controller="mainCtrl" class="row-fluid">
<form class="row-fluid">
<div class="container-fluid">
<input type="text" ng-model="name" typeahead="name for name in names | filter:$viewValue" />
</div>
</form>
</div>
<h5>Comment : </h5>
<textarea ng-model="test.test_content"></textarea>
<input type="submit" id="submit" value="Submit" />
</form>
As you can see i have 2 controllers.The main one is a form , the second one is an input box that allows the user to search the name in a list using typeahead.
I want to be able to acces the content of ng-model="name" in my child controller (mainctrl) in my TestController.
I've tried to access it directly with $scope.name but it doesn't work.
I've also tried test.name as the ng-model and it didn't work either.
I'm sending the data of my TestController to my server and would like to send the data ( name ) from my mainCtrl aswell, directly from TestController. So that when my user click submit it send both the name + the test_content in $http.post request.
Anyone know how to do that ?
Thanks
I've found this but it didn't really help.. https://fdietz.github.io/recipes-with-angular-js/controllers/sharing-models-between-nested-controllers.html
edit:
my search controller
.controller("mainCtrl", function ($scope, $http) {
$scope.selected = '';
$http.get('/feedbacks/search.json').
then(function(response) {
$scope.succes = " ok "
$scope.names = response.data;
// this callback will be called asynchronously
// when the response is available
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
$scope.succes = " error"
});
});
my form controller :
angular.module('FeedbackTool')
.controller('TestController',['$scope', '$http', function($scope,$http) {
$http.get('/feedbacks.json').
then(function(response) {
$scope.succes = " ok "
$scope.list = response.data;
// this callback will be called asynchronously
// when the response is available
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
$scope.succes = " error"
});
$scope.submit = function() {
$http.post('/feedbacks.json', { data:this.test }).
then(function(response) {
$scope.succes = 'sent';
// this callback will be called asynchronously
// when the response is available
}, function(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
$scope.succes = 'fail';
});
};
}]);

The parent scope is accessible from within the child scope, but the child scope is not accessible from the parent. Typically the way to deal with this is to assign values from the child scope to properties already defined in the parent. In your case I think you just need to use test.name as the ng-model expression.

Related

syncing variable across pages using angularJs

I have a html page with a link as follows:
<div ng-if="!adminCtrl.valid">
<div><a target="_blank" ng-href="https://api.instagram.com/oauth/authorize/?client_id=xxx&redirect_uri=http://localhost:8888/igSuccess.html&response_type=token">Authorize to Instagram</a><br/></div>
</div>
This goes to redirect page on success where the code is
<div ng-controller="AdminController">
<h2>You can close this tab/window</h2>
</div>
The control is same for both pages as follows:
app.controller('AdminController', ['$scope','$routeParams','$location', function($scope,$routeParams,$location){
var actrl = this;
actrl.valid = false;
var token = $location.absUrl();
if(token.indexOf('access_token') > -1){
console.log('found token so will do special');
actrl.valid = true;
$scope.$apply();
}
}}
I am expecting the link to disappear once the new page opens as i am updating the valid variable value.
i know the flaw seems to be the cross page communication. so how to deal with it?
Controllers are 'flushed' when you change views. To keep data from a view/controller to another, store your data within a Service.
UPDATE
controller:
app.controller('AdminController', [
'$scope', '$routeParams', '$location', 'ExampleService', function ($scope, $routeParams, $location, ExampleService) {
var actrl = this;
// Watches the service's value for changes and applies it to the controller
$scope.$watch(function(){return ExampleService.valid}, function(newValidValue){
actrl.valid = ExampleService.valid;
});
var token = $location.absUrl();
if (token.indexOf('access_token') > -1) {
console.log('found token so will do special');
ExampleService.valid = true;
// No need for this
// $scope.$apply();
}
}
}
Service:
app.service('ExampleService', [
function () {
//All properties here are kept through-out your app's life time
this.valid = false; // Init to false
}
}
To share data between Controllers in Angular JS, use a named Service to encapsulate the data. In your case, I would typically define an Auth service that provides a few methods for getting and setting the access_token for a user:
module.factory('Auth', function(){
return {
isValid: function(){ /* Check that a User is authenticated... */ },
setToken: function(token){ /* Store the token somewhere... */ },
getToken: function(){ /* Fetch the token from somewhere... */ }
};
});
To share data across "pages" -- tabs or windows in your browser -- even in a Single Page Application (SPA) like this, store the data in cookies or localStorage. You can use angular-local-storage by grevory (GitHub) to abstract the details of using localStorage with a cookie fall-back in non-compatible browsers.
The reason that one page cannot see the valid value defined in the other is because each page gets a separate instance of AdminController, each of which get their own separate instance of $scope tied to their respective DOM elements. Setting valid on the $scope of the redirect landing page has not effect on the completely detached $scope instance in the originating page.
You'd encounter similar difficulties with a trivial same-page example (CodePen):
angular.module('scope-example', [])
.controller('ExampleCtrl', function($scope) {
$scope.value = 'Initial Value';
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<form class="pure-form" ng-app="scope-example">
<fieldset ng-controller="ExampleCtrl">
First instance of <code>ExampleCtrl</code>:
<br>
<input ng-model="value">
<label>{{value}}</label>
</fieldset>
<fieldset ng-controller="ExampleCtrl">
Second instance of <code>ExampleCtrl</code>:
<br>
<input ng-model="value">
<label>{{value}}</label>
</fieldset>
<fieldset ng-controller="ExampleCtrl">
Third instance of <code>ExampleCtrl</code>:
<br>
<input ng-model="value">
<label>{{value}}</label>
</fieldset>
</form>
Even though each of the <fieldset> elements have identical ng-controller directives associated, each gets its own instance of ExampleCtrl and $scope, so the value property isn't shared between them. This holds true for any directive.

AngularJs and two Controllers

I am writing an application that is running AngularJs & Bootstrap on the frontend - and using angularjs' $http to make calls to an API...I have two controllers within one app -- the first controller displays the person's name and the second controller displays a list of performances that this person has seen...
<html lang="en" ng-app="myapp">
...
<div class="panel-body" ng-controller="Ctrl">
<div class="row">
<div class="col-sm-3" style="height: 50px;">
<div class="well well-sm">
First Name:
{{ user.fname || "empty" }}
</div>
</div>
<div class="col-sm-3" style="height: 50px;">
<div class="well well-sm">
Last Name:
{{ user.lname || "empty" }}
</div>
</div>
</div>
</div>
<div class="list-group" ng-controller="Ctrl2">
<div ng-repeat="obj in performances" >
<div class="list-group-item">
<h4 class="list-group-item-heading">
<span class="badge">
{{$index+1}}</span> {{ obj["Perf_name"] || "empty" }}</h4>
<p class="list-group-item-text">
Date: {{ obj["Perf_dt"] || "empty" }}<br />
Theater: {{ obj["Theater"] || "empty" }}<br />
</p>
</div>
</div>
</div>
...
</html>
In the JS file I have the following code
Declare my variables and module
var app = angular.module("myapp", ["xeditable"]);
var q = getUrlParameter("q"); // get the query param
var baseurl = "https://some.domain/service/getCRM";
var baseurl2 = "https://some.domain/service/getPerformances";
Create a service to be used by both controllers
app.service('crmService', function () {
this.id = 0;
this.getID = function () { return this.id };
this.setID = function (newid) { this.id = newid; };
});
Define Controller #1
app.controller('Ctrl', function ($scope, $http, crmService) {
$http({
method: 'GET',
url: baseurl,
respondType: 'json',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
params: {
phone_no: q
}
}).then(function successCallback(response) {
$scope.user = {
fname: response.data[0]["FirstName"],
lname: response.data[0]["LastName"]
}
crmService.setID(response.data[0]["CRM_ID"]);
console.log("*** OUTPUT LINE 1: " + crmService.getID());
}, function errorCallback(response) {
console.log("**ERROR");
console.log(response);
});
});
Define Controller #2
app.controller('Ctrl2', function ($scope, $http, crmService) {
console.log("*** OUTPUT LINE 2: " + crmService.getID());
var promise = $http.get(baseurl2 + "?crm_id=" + crmService.getID());
promise.then(
function (response) {
$scope.performances = response.data;
});
});
Console output
*** OUTPUT LINE 2: 0
*** OUTPUT LINE 1: 123456
Questions:
Why is the second controller outputting before the first, I assume it is because AngularJs by default executes its code asynchronously, and if that's the case, why did the "promise" not work?
What I need to happen is, I make my GET call in the first controller block and assign the customer ID using my service...I then go on to the second controller block and using the customer ID, from my service (which was set within the first controller block) - get the customer's performances
So I have to first controller working fine, but the second controller keeps thinking that the customer ID is 0 - when it should be 123456
What am I missing?
FYI: when I hard code an ID for the second controller to use, I get actual performance records back - so I know everything will work once I figure out how to share values between my controllers
Why is the second controller outputting before the first
Because the second controller logs the ID as soon as it's instanciated, whereas the first one logs it once it has received, asynchronously, a response to an HTTP request. The promise did work, since the output is logged in the console.
What I need to happen is, I make my GET call in the first controller block and assign the customer ID using my service...I then go on to the second controller block and using the customer ID, from my service
You're doing your job more complex than it should be: using a single controller would be much easier here, since the model of the second one depends on what the first one does.
You could broadcast an event from the first controller to signal that the ID has been set, and listen to the event in the second one to get the data at tis time.
app.controller('Ctrl', function ($scope, $http, $rootScope, crmService) {
$http({
...
}).then(function successCallback(response) {
...
crmService.setID(response.data[0]["CRM_ID"]);
console.log("*** OUTPUT LINE 1: " + crmService.getID());
$rootScope.$broadcast('crmIdChanged');
};
});
app.controller('Ctrl2', function ($scope, $http, crmService) {
function getPerformances() {
var id = crmService.getID();
if (id != 0) {
var promise = $http.get(baseurl2 + "?crm_id=" + crmService.getID());
promise.then(
function (response) {
$scope.performances = response.data;
});
}
}
getPerformances();
$scope.$on('crmIdChanged', getPerformances);
});
You could even broadcast the even from the service itself.

How to send data from one controller to another controller in angularjs

Below is my playlist json data coming from playlist controller.
{
"id":18,
"file":{"url":"/uploads/playlist/file/18/01_-_MashAllah.mp3"},
"event_id":23,"created_at":"2015-11-11T10:33:52.000Z",
"updated_at":"2015-11-11T10:33:52.000Z",
"name":"01 - MashAllah.mp3"
},
{
"id":19,
"file":{"url":"/uploads/playlist/file/19/02_-_Laapata.mp3"},
"event_id":19,"created_at":"2015-11-11T10:50:01.000Z",
"updated_at":"2015-11-11T10:50:01.000Z",
"name":"02 - Laapata.mp3"
}
Now i want to bind id and name to a playerController am i doing something like this
<div ng-controller="playlistsController">
<div ng-repeat="playlist in playlists">
<div ng-controller='PlayerController'>
<input type=hidden ng-model="ID" ng-init="ID=playlist.id">
<input type=hidden ng-model="Name" ng-init="Name=playlist.name">
</div>
</div>
</div>
and in controller
.controller('PlayerController',['$scope',function($scope) {
console.log($scope.ID);
console.log($scope.name);
}]);
but the console is showing undefined.
i don't know where i am going wrong as i am new to angular.
METHOD 1:
The best way to share data between controllers is to use services. Declare a service with getters and setters and inject into both the controllers as follows:
service:
app.service('shareData', function() {
return {
setData : setData,
getData : getData,
shared_data : {}
}
function setData(data) {
this.shared_data = data
}
function getData() {
return this.shared_data
}
})
With your service defined, now inject into both the controllers and use the parent controller (in your case) to set the data as follows:
app.controller('playlistsController', function(shareData) {
//your data retrieval code here
shareData.setData(data);
})
And finally in your child controller, get the data:
app.controller('PlayerController', function(shareData) {
$scope.data = shareData.getData();
})
METHOD 2:
Since, you have to communicate data from parent controller to child controller, you can use $broadcast as follows:
parent controller:
//retrieve data
$scope.$broadcast('setData', data);
And receive the data in child controller:
$scope.$on('setData', function(event, args) {
$scope.data = args;
})
First controller code is executed, then angular starts proceed html this controller is attached to. So just move your variable initialization to controller:
$scope.Name = $scope.playlist.name;
$scope.ID = $scope.playlist.id;
or just use original variables (if you dont need copy of them)
<input type=hidden ng-model="ID=playlist.id">
<input type=hidden ng-model="Name=playlist.name">
or you may leave it as is - it works disregarding that you don't see values in log.
You should use $parent:
JSFiddle
HTML:
<div ng-controller="playlistsController">
<div ng-repeat="playlist in playlists">
<div ng-controller='PlayerController'>
<input type="text" ng-model="$parent.playlist.id">
<input type="text" ng-model="$parent.playlist.name">
</div>
</div>
</div>
JS:
app.controller('PlayerController', function ($scope, $sce) {
console.log($scope.$parent.playlists);
});

Why angularjs controller is not called when i click the button again?

Hi i have a webpage like this,
left side has button,
right side is the area for ng-view (in my case, several checkboxes and submit button)
When i click the button, it'll
1. using the route provider, it'll reach its controller and template URL.
2. The controller will query some info from back end side (node.js)
3. The info above will be used by template URL to display initial checkbox options.
Now this procedure works fine for the 1st time. But when i click the button again, i was hoping it'll call its controller again, but from debugger, seems nothing happened, controller is not called.
So very confused, why is this please ???
in the server side,
app.get('/2getMyDiagValue', function(req, res)
{
console.log("get my diag");
var v1=0, v2=0;
var shellCmd = "... some executable ... ";
exec(shellCmd, function(error, stdout, stderr) {
if(error) {
console.log("Error running getting sid");
} else {
// get v1 and v2 from stdout
}
res.json( {"mystuff1":v1, "mystuff2":v2} );
});
app.post('/2setMyDiagValue', function(req, res)
{
// get the checkbox options from webpage,
// and save them in the backend
}
in the client side,
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.when('/5MyDiag', {
templateUrl: 'partials/MyDiag.html',
controller: 'MyDiagController'
});
}
]);
app.controller('myDiagController', function($scope, $http, $routeParams, QueryMyService) {
// using http.get() to get existing my setting from server side
QueryMyService.getInfoFromUrl7('/2getMyDiagValue').then(function(result) {
$scope.formData = result.formDataObjects;
}, function(error) {
alert("Error");
} );
$scope.submitForm = function() {
console.log("posting form data ...");
$http.post("/2setMyDiagValue",
JSON.stringify($scope.formData)).success(function(){} );
};
});
app.factory('QueryMyService', function($http, $q, $location) {
var factory = {};
var browserProtocol = 'http';
var address = 'localhost';
var server = browserProtocol + '://' + address;
//////////////////////////////////////////////////////////
factory.getInfoFromUrl7 = function(myUrl) {
var deferred = $q.defer();
$http.get(myUrl).success(function(data) {
deferred.resolve(data);
}).error(function(){
deferred.reject();
});
return deferred.promise;
}
return factory;
}
checkbox webpage itself: MyDiag.html
<form ng-submit="submitForm()" ng-controller="myDiagController">
<div class="control-group" style="color:black">
<label>My Checkbox</label>
<div class="checkbox">
<label class="checbox-inline" >
<input class="big-checkbox" type="checkbox" ng-model="formData.myStuff1"
ng-true-value="1" ng-false-value="0" ng-checked="formData.myStuff1 == 1">
<h4>Message 1</h4>
<input class="big-checkbox" type="checkbox" ng-model="formData.myStuff2"
ng-true-value="1" ng-false-value="0" ng-checked="formData.myStuff2 == 1">
<h4>Message 2</h4>
</label>
</div>
<br>
<input class="btn-primary" type="submit">
</form>
index.html
<div class="container">
<a class="btn btn-md btn-info custom-btn" ng-href="#/5MyDiag">Diagnostics</a>
</div>
Since i need to remove company names in the variable, there might be mismatch, but idea is the same. Thank you for your help.
Talked with guru, it's supposed to be so, if angular feels no change to the web GUI, e.g. in my case, i clicked the same button twice, it won't call the route provider again for the 2nd click.
If you knew this concept, you don't need to read my code to answer this question.
Wish i can get the 4 points back.

what is the way to iterate the $resource.get other than doing $promise.then

Once I receive the document say $scope.var = $resource.get(/*one record*/).. I would need to read the received nested object structure (which is now in $scope.var) in order to display each and every field.
I am not able to access the key-value pairs that are present in the $scope.var. So I found a way in doing this using $scope.var.$promise.then(callback) but the format of the $scope.var is changed.
for example:
before parsing:
When I console to see what is in $scope.var, it shows as -
Resource: {$Promise: Promise, $resolved: false}
/*key-value pairs of the object*/
after parsing using $promise.then:
Here the console says
Object {_id: "...", ...}
/*key-value pairs of the object*/
Because of the above format difference I am facing the problem when trying to $update using $resource. Which says $update is not a function.
Is there any other way to access key-value pairs from the $resource.get other than using $promise.then?
Edit: Here is my original code:
Contact.service.js
'use strict';
angular.module('contacts').factory('Contact', ['$resource', function($resource) {
console.log('Iam cliked');
return $resource('/api/contacts/:id', {id: '#_id'}, {
update: {
method: 'PUT'
}
});
}]);
EditController.js
'use strict';
angular.module('contacts').controller('EditController', ['$scope', '$stateParams' ,'$location', '$rootScope', 'Contact',
function($scope, $stateParams, $location, $rootScope, Contact) {
$rootScope.PAGE = 'edit';
$scope.contact = {};
$scope.singleContact = Contact.get({ id: $stateParams.id});
console.log($scope.singleContact);
$scope.singleContact.$promise.then(function(data){
angular.forEach(data, function(value, key) {
if(key !== 'additions')
$scope.contact[key] = value;
else {
angular.forEach(data.additions, function(value, key) {
$scope.contact[key] = value;
});
}
});
console.log($scope.contact);
});
/*parsing my received nested json doument to have all straight key-value pairs and binding this $scope.contact to form-field directive 'record=contact'
I am successful doing this and able to display in web page but when I try to $update any of the field in the webpage it doesnt update. Because it is not being Resource object but it is just the object
please see the images attached[![enter image description here][1]][1]*/
$scope.delete = function(){
$scope.contact.$delete();
$location.url('/contacts');
};
}
]);
Edit.html
<section data-ng-controller='EditController'>
<h2>{{contact.firstName}} {{contact.lastName}}</h2>
<form name='editContact' class='form-horizontal'>
<form-field record='contact' recordType='contactType' field='firstName' required='true'></form-field>
<form-field record='contact' recordType='contactType' field='lastName' required='true'></form-field>
<form-field record='contact' recordType='contactType' field='{{k}}' ng-repeat=' (k, v) in contact | contactDisplayFilter: "firstName" | contactDisplayFilter: "lastName" | contactDisplayFilter: "__v" | contactDisplayFilter: "_id" | contactDisplayFilter: "userName"'></form-field>
<new-field record='contact'></new-field>
<div class='row form-group'>
<div class='col-sm-offset-2'>
<button class='btn btn-danger' ng-click='delete()'>Delete Contact</button>
</div>
</div>
</form>
form-field.html
<div class='row form-group' ng-form='{{field}}' ng-class="{'has-error': {{field}}.$dirty && {{field}}.$invalid}">
<label class='col-sm-2 control-label'>{{field | labelCase}} <span ng-if='required'>*</span></label>
<div class='col-sm-6' ng-switch='required'>
<input ng-switch-when='true' ng-model='record[field]' type='{{recordType[field]}}' class='form-control' required ng-change='update()' ng-blur='blurUpdate()' />
<div class='input-group' ng-switch-default>
<input ng-model='record[field]' type='{{recordType[field]}}' class='form-control' ng-change='update()' ng-blur='blurUpdate()' />
<span class='input-group-btn'>
<button class='btn btn-default' ng-click='remove(field)'>
<span class='glyphicon glyphicon-remove-circle'></span>
</button>
</span>
</div>
</div>
<div class='col-sm-4 has-error' ng-show='{{field}}.$dirty && {{field}}.$invalid' ng-messages='{{field}}.$error'>
<p class='control-label' ng-message='required'>{{field | labelCase}} is required.</p>
<p class='control-label' ng-repeat='(k, v) in types' ng-message='{{k}}'>{{field | labelCase}} {{v[1]}}</p>
</div>
function in form-field.js directive
$scope.blurUpdate = function(){
if($scope.live!== 'false'){
console.log($scope.record);
$scope.record.$update(function(updatedRecord){
$scope.record = updatedRecord;
});
}
};
So in the above $update gives error. saying not a function.
I want the same format in $scope.singleContact and $scope.contact. How can I achieve this?
I don't know if I understand but you want something like this:
Contact.get({id: $stateParams.id}, function (contact){
$scope.var = contact;
});
right?
It will return te promise and you will be able to use the data before loading the templates
Okay, a couple things:
Promises will not return values synchronously
You have to use a then function call. The line
$scope.singleContact = Contact.get({ id: $stateParams.id});
will not put your data into the $scope.singleContact variable immediately because that line returns a Promise and goes onto the next line without waiting for the web request to return. That's how Promises work. If you aren't familiar with them, it might not be a bad idea to read up on them. A consequence of this is that everything inside your controller (or wherever this code is) that needs a working value in $scope.singleContact needs to be inside of a then block or in some way guaranteed to be run after the promise is resolved.
So I think you want (as #EDDIE VALVERDE said):
Contact.get({ id: $stateParams.id}, function(data) {
$scope.singleContact = data;
$scope.contact = data;
});
I'm not sure what the rest of the code is doing but it looks to me like it is trying to do a deep copy of $scope.singleContact. My guess is that you're just doing this cause the promise stuff wasn't working and you can remove it...
Let me know if I missed something.
Ah, now I think I understand. Okay, for 1 I would not add $resource objects to your scope. IMO the scope is intended to be a Model in the MVC paradigm. A $resource is kindof like a model, but it's performing network calls and it's got a bunch of stuff other than data. If you put $resource in your model you remove any clear layer where you can manipulate and transform the data as it comes back from the server. And I'm sure there are other reasons. So I would not add $resource objects like Contact directly to the scope. That being said, I think you want to add a new update function, like you did with delete, only something like this:
$scope.update= function(args){
//make network call and update contact
Contact.$update({/*data from args?*/});
//get the data i just saved
Contact.$get({/* id from args */}, function(result) {
$scope.contact = result;
});
//other stuff?
};
That's my first thought, anyway...

Resources