I am using Angular JS 1.5.6 components to build dynamically a form. The hierarchy is the following : index.html calls component my-view which calls component my-form which calls unitary components like inputs and button.
The issue is that the data binding is not working because any modification in input components is not taken into account into my-view component.
Besides I have a weird behavior, each time I update input value, a call is made to view component function.
I have plunkered this, the submit button triggers console.log (so need to open firebug to see it in action).
Here is my index.html
<body ng-app="MyApp">
<my-view></my-view>
</body>
Here is myView.html
<div class="container">
<h2>With component myform</h2>
<my-form form-elements="
[{type:'input', label:'First name', placeholder:'Enter first name',model:$ctrl.userData.firstName, id:'firstName'},
{type:'input', label:'Last name', placeholder:'Enter last name',model:$ctrl.userData.lastName, id:'lastName'},
{type:'button', label:'Submit', click:$ctrl.click()}]"></my-form>
</div>
<div class="container">
<h2>Check data binding</h2>
<label>{{$ctrl.userData.firstName}}</label>
<label>{{$ctrl.userData.lastName}}</label>
</div>
Here is myView.js
(function() {
'use strict';
angular.module('MyApp').component('myView', {
templateUrl: 'myView.html',
controller: MyViewController,
bindings: {
viewFormElements: '<'
}
});
function MyViewController() {
this.userData = {
firstName: 'François',
lastName: 'Xavier'
};
function click() {
console.log("Hello " + this.userData.firstName + " " + this.userData.lastName);
}
this.click = click;
}
})();
I manage to solve my issue with 2 way binding and by putting form-element in an object instead of putting it directly in the view ($ctrl.formElements). It is on plunker.
myView.html
<div class="container">
<h2>With component myform</h2>
<my-form form-elements=$ctrl.formElements></my-form>
</div>
<div class="container">
<h2>Check data binding</h2>
<label>{{$ctrl.formElements}}</label><br />
</div>'
Related
I want to embed a nested component in a page.
(A page is actually a controller that can be reached via the $routeProvider service)
And I want to bring data from the main component to its child component and vice versa - in order to make all of the components in the page and the page itself talking with each other in a full data binding.
I success to send data from parent to child with specific bindings attributes, however, I am not getting a way to bring data from child to parent.
// lobby.js - the main page.
// we can reach this page via browser by the $routeProvider service
app.config(($routeProvider) => {
$routeProvider
.when("/", {
templateUrl : "screens/lobby/lobby.html"
})
});
app.controller("lobby", ($scope, datepickerService) => {
$scope.title = "Welcome to Lobby screen!";
$scope.order = {};
$scope.send = function() {
console.log($scope.order);
};
});
Lobby.html
<!-- This is lobby.html file -->
<!-- Which is the html template of the main page (lobby.js) -->
<link rel="stylesheet" href="screens/lobby/lobby.css">
<div class="lobby" ng-controller="lobby">
<date-picker type="default" model="startDate"></date-picker>
<date-picker type="default" model="endDate"></date-picker>
<button type="button" name="button" ng-click="send()">Send</button>
</div>
Now as you can see, in the lobby.html file I have a nested component which is <date-picker></date-picker>. From parent I pass to this child component two attributes: type and model.
Now lets see this component functionality:
// datepicker.js component (actually defined as a directive)
// Initializing a datepicker plugin from jQuery UI Lib.
app.directive("datePicker", (datepickerService) => {
return {
templateUrl: "/shared/datepicker/datepicker.html",
scope: {
model: "#",
type: "#",
},
link: function(scope, elements, attrs) {
$(function() {
setTimeout(function () {
$("." + scope.model).datepicker({
onSelect: function(value) {
value = datepickerService.correct(value);
$("." + scope.model).val(value);
console.log(value);
}
});
}, 200);
});
}
}
});
datepicker.html
<!-- datepicker.html the datepicker html template -->
<!-- Successfuly getting the datepicker to be loaded and work -->
<box ng-show="type=='default'">
<input type="text" class="{{model}}" readonly>
</box>
Now the problem: notice the:
// lobby.js
$scope.send = function() {
console.log($scope.order);
};
in the lobby.js file.
I need this to send the actual startDate and endDate to a remote server. However I cannot access this data! $scope.order remains blank.
I have tried using components instead of directives I have tried ng-include I have tried more lot of things that I wont bother you with, since I have spent on it more than 3 days.
How can I work with nested components so all of the data will be shared through each of them, including the main page in AngularJS in order to create a scaleable modern app?
Thanks.
For sending data from parent to child angular provides the $broadcast() method and for sending data from child to parent it provides the $emit() method.
More info:
http://www.binaryintellect.net/articles/5d8be0b6-e294-457e-82b0-ba7cc10cae0e.aspx
I think you have to reference your startDate and endDate within your order object. Right now it seems you save those directly on your $scope.
Try this to verify:
console.log($scope.order, $scope.startDate, $scope.endDate);
add "order." in front your objects within the model attribute.
<!-- This is lobby.html file -->
<!-- Which is the html template of the main page (lobby.js) -->
<link rel="stylesheet" href="screens/lobby/lobby.css">
<div class="lobby" ng-controller="lobby">
<date-picker type="default" model="order.startDate"></date-picker>
<date-picker type="default" model="order.endDate"></date-picker>
<button type="button" name="button" ng-click="send()">Send</button>
</div>
Also, you might also need to change the attribute definition of your component to use bidirectional binding. Use "=" instead of "#". # only represents a copy of the value when getting passed to your component and not saved back to the original object.
...
scope: {
model: "=",
type: "#",
},
...
Update:
Please find my working Plunker here https://embed.plnkr.co/2TVbcplXIJ01BMJFQbgv/
I'm working on a small app in AngularJS. My project contain a Body.html file that contain 3 views: SideMenu, Header and Content, each with each own Controller and a MainController as there parent - the controller of the Body.html.
Can the header's controller change a property in the side-menu - the open/close status of the side-menu.
And Can the side-menu controller change a property in the header - the header's text.
I can use the main controller, since both of the header's controller and the side-menu controller can reference the main controller. But the data won't be consist. Updating the data from the 1st controller wan't effect the data in the 2nd controller (without the use of $watch).
Can both the side-menu's controller and the header's controller (sibling controllers) communicate with each other? without the help of there parent?
Body.html
<div>
<!-- Header placeholder -->
<div ui-view="header"></div>
<!-- SideMenu placeholder -->
<div ui-view="sideMenu"></div>
<!-- Content placeholder -->
<div ui-view></div>
</div>
Header.html
<div>
{{ headerCtrl.text }}
</div>
<div ng-click="headerCtrl.openSideMenu()">
--Open--
</div>
HeaderController.js
// sideMenuCtrl = ???
headerCtrl.text = "Title";
headerCtrl.openSideMenu = function()
{
sideMenuCtrl.isSideMenuOpen = true;
};
SideMenu.html
<div ng-class={ 'side-menu-open': sideMenuCtrl.isSideMenuOpen }>
<div ng-repeat="menuItem in sideMenuCtrl.sideMenuItems"
ng-click="sideMenuCtrl.selectMenuItem(menuItem)">
{{ menuItem.text }}
</div>
</div>
SideMenuController.js
// headerCtrl = ???
sideMenuCtrl.selectMenuItem = function(menuItem)
{
headerCtrl.text = menuItem.text;
}
As stated in my comment, you can use an AngularJS service to share some data between your controllers.
app.service('AppContextService', function(){
this.context = {
isSideMenuOpen: false
};
});
app.controller('SideMenuCtrl', ['$scope', 'AppContextService', function($scope, AppContextService) {
// exposing the application context object to the controller.
$scope.appContext = AppContextService.context;
}]);
app.controller('HeaderCtrl', ['$scope', 'AppContextService', function($scope, AppContextService) {
$scope.appContext = AppContextService.context;
$scope.openSideMenu = function()
{
$scope.appContext.isSideMenuOpen = true;
};
}]);
Then adapt the HTML to use your shared appContext object.
<div ng-class={ 'side-menu-open': appContext.isSideMenuOpen }>
[...]
</div>
Here is a working fiddle that illustrates the issue: fiddle
This answer covers the use of a service to fit your needs but I am sure that there are other (and perhaps better) ways to tackle the problem which might involve other Angular feature, or even some overall application refactoring.
To dig a little deeper, this SO topic might be a good start: difference between service, factory and providers
Having another problem with figuring out Angular Typescript; this time when it comes to directives with controllers. Trying to pass an object from a page to a directive and then use that object in the controller of the directive. Maybe this isn't the correct approach, but it seems to make sense to me; just can't figure out how to access the object in the controller.
HTML from page:
<div>
<section>
On View: {{ee.obj.name}}
<image-upload obj="ee.obj"></image-upload>
</section>
</div>
Directive template:
<div>
On directive: {{obj.name}}
On controller: {{iuc.obj.name}}
<div class="row">
<div class="col-md-4 text-primary h4">
Add Images
</div>
</div>
<div class="row">
<div class="col-md-3">
<input type="file" multiple ng-model="iuc.imageUploads" ng-change="iuc.uploadImages()" />
</div>
</div>
<div class="row">
<div class="col-md-3 dropzone"></div>
</div>
</div>
Directive typescript:
/// <reference path="../../../scripts/_all.ts" />
module ObjConfig {
'use strict'
export class ImageUpload implements ng.IDirective {
static instance(): ng.IDirective {
return new ImageUpload();
}
restrict = 'E';
replace = true;
templateUrl = '../../../../App/AppConfig/Views/Directives/ImageUpload.html';
scope = {
obj: '='
};
controller = imageUploadCtrl;
controllerAs = 'iuc';
}
export class imageUploadCtrl {
obj: OBJBase;
imageUploads: OBJImage[];
constructor() {
}
uploadImages() {
//THIS IS WHERE I WANT TO ACCESS OBJ
//this.imageUploads.forEach((iu) => { this.obj.images.push(iu); });
}
}
angular.module('ObjConfig').directive('imageUpload', ImageUpload.instance);
}
When I use "this.obj" in the method, it comes back as undefined so obviously the controller "obj" doesn't get automatically wired up to the directive "obj". The ee.obj.name on the page shows value, the obj.name at the top of the directive template shows value, but the iuc.obj.name does not show value. So I've been trying to find a way to link the directive obj to the controller obj and I've tried using a link function to use ng.IAttributes, but that doesn't give me the object; it gives me ee.obj.
Any help would be greatly appreciated.
You should be able to accomplish what you are trying to do using bindToController: true.
This feature is documented in the $compile documentation, so it's not easy to locate, but it does what you want.
When an isolate scope is used for a component (see above), and controllerAs is used, bindToController: true will allow a component to have its properties bound to the controller, rather than to scope. When the controller is instantiated, the initial values of the isolate scope bindings are already available.
I have a template, which is loaded using $templateRequest. I have a textarea in the template. The textarea populates the value, that is set in the controller. But when I am trying to access the textarea in the controller method, it does not update the value.
Template
<div class="panel add-notes">
<div class="panel-body">
<textarea ng-model="quickNote" rows="5"/>
<button ng-click="saveNotes()">Add Note</button>
</div>
</div>
</div>
Method to load the template
$scope.displayPanel = function(templateName){
var templateURL = resourceURL+templateName+".html";
$templateRequest(templateURL).then(function(template) {
$compile($("#modalContent").html(template).contents())($scope);
},function() {
//ERROR HANDLER
});
};
Controller Method
$scope.saveNotes = function(){
console.log($scope.quickNote);
}
In the console log, the value of quickNote is displayed as undefined. Any resolution?
I have the following problem, I want to call a function of another controller from within a controller I want to use for a guided tour (I'm using ngJoyRide for the tour). The function I want to call in the other controller is so to say a translator (LanguageController), which fetches a string from a database according to the key given as parameter. The LanguageController will, if the key is not found, return an error that the string could not be fetched from the database. In my index.html fetching the string works, but I want to use it in the overlay element of my guided tour, which does not work, but only shows the "not fetched yet"-error of the LanguageController.
My index.html looks like this:
<body>
<div class="container-fluid col-md-10 col-md-offset-1" ng-controller="LangCtrl as lc" >
<div ng-controller="UserCtrl as uc" mail='#email' firstname='#firstname'>
<div ng-controller="GuidedTourCtrl as gtc">
<div ng-joy-ride="startJoyRide" config="config" on-finish="onFinish()" on-skip="onFinish()">
...
{{lc.getTerm('system_lang_edit')}}
...
</div>
</div>
</div>
</div>
</body>
The controller I'm using for the guided Tour looks like this:
guidedTourModule.controller('GuidedTourCtrl',['$scope', function($scope) {
$scope.startJoyRide = false;
this.start = function () {
$scope.startJoyRide = true;
}
$scope.config = [
{
type: "title",
...
},
{
type: "element",
selector: "#groups",
heading: "heading",
text: " <div id='title-text' class='col-md-12'>\
<span class='main-text'>"\
+ $scope.lc.getTerm('system_navi_messages') + "\
text text text text\
</span>\
<br/>\
<br/>\
</div>",
placement: "right",
scroll: true,
attachToBody: true
}
];
...
}]);
And the output I ultimately get looks like this for the overlay element:
<div class="row">
<div id="pop-over-text" class="col-md-12">
<div id='title-text' class='col-md-12'>
<span class='main-text'>
not fetched yet: system_navi_messages
text text text text
</span>
<br/>
<br/>
</div>
</div>
</div>
...
I hope someone can see the error in my code. Thanks in advance!
Things needs clarity are,
How you defined the 'getTerm' function in your Language controller, either by using this.getTerm() or $scope.getTerm(). Since you are using alias name you will be having this.getTerm in Language controller.
Reason why you are able to access the getTerm function in your overlay element is, since this overlay element is inside the parent controller(Language Controller) and you are referencing it with alias name 'lc' while calling the getTerm function. Thats' why it is accessible.
But the string you pass as a parameter is not reachable to the parent controller. that's why the error message is rendered in the overlay HTML.
Please make a plunker of your app, so that will be helpful to answer your problem.