Why is there no binding between React.Js template and Angular model? - javascript

I am trying to mix Angular and React.JS. You can see my code here. Most examples I have seen involve an input written in basic Angular, and then output written in React like in this example. I am trying to do the opposite. I have two input fields as React components created in a directive and then an output of the model done with Angular.
I cannot get the output to reflect changes in the model. The model being $scope.formData = {name: "name", email: "email"};
I tried to follow the example in this video, and create an isolated scope in the directive
scope: {
formname: '=formname',
formemail:'=formemail'
},
and then pass the proper values through to the React template. It doesn't seem to have any affect. My input is not connected to the formData. In my non-React version of this, the model data fills in the input immediately and the output reflects changes to the input.
Because binding that data in an Angular style is breaking, I tried to have the React components update the model in a more Reactish way. I wrote a function in the controller called update_model,
$scope.update_model = function(data_type, updated_data){
$scope.$apply(function() {
$scope.formData[data_type] = updated_data;
});
console.log($scope.formData);
}
I then passed that function through the directive to the React template using scope.update_model, in the link function.
link: function(scope, element) {
React.renderComponent(Form_Profile_Component({update_model: scope.update_model, name_label: 'name', email_label: 'email', name_data: scope.formname, email_data: scope.formemail}), element[0]);
}
Now in the template, the input elements have and onChange event detector that will call this, and update the Model. The Model is supposed to be displayed at the bottom of the page with
<pre>
{{ formData }}
</pre>
The console.log shows a change in formData, but the {{formData}} display does not change.
Why won't my display {{formData}} show what is going on in the actual formData object?
EDIT: Just to make sure there wasn't a problem with {formData} I threw <input ng-model="formData.name"> in a few places in form.html, and there was no problem. The two way binding worked there.
EDIT 2: I tried adding the code below to the directive.
link: function(scope, element) {
scope.$watch('formData.name', function(newVal, oldVal) {
console.log("---");
React.renderComponent(Form_Profile_Component({update_model: scope.update_model, name_label: 'name', email_label: 'email', name_data: scope.formData.name, email_data: scope.formData.email}), element[0]);
});
}
I believe that when there is a change in formData, it will call this function and update the React component, or at the very least print '---' to the console. When I use the React input or the proper input nothing happens, which is not so surprising at this point. But when I tried the working input from the first edit, which does alter {{formData}}, nothing happens. It doesn't trigger the watch at all, I don't understand why.

You're on the right track with your 2nd edit where you put the renderComponent call inside a scope.$watch. This will make sure that the react component is re-rendered every time your watch variable formData.name changes.
However if you're looking for the changes you make within your React Component to propagate back out to angular, you need to make sure that the callback function you're passing to react has scope.$apply() call in it. Either encapsulating the change, or just after it.
Here's a quick example:
In your controller:
$scope.myVar = 'foo';
$scope.setVar = function(value) {
$scope.myVar = value;
$scope.$apply();
};
In your directive's link function (assuming no restricted scope, else change name as needed)
scope.$watch('myVar', function() {
React.renderComponent(
MyComponent({
myVar: scope.myVar,
setVar: scope.setVar
}),
element.get(0)
);
});
Then just call the this.props.setVar function from within your react component with the new value and it'll update correctly on the model.

Related

How to use reactive forms inside ng-template

I have just started with Angular 4 and I need to develop a CRUD grid, where the user can add, edit or delete rows.
During my research I found this article where it shows how to create the grid and also the actions: Angular 4 Grid with CRUD operations.
Looking at his code, what called my attention was the way he is using the ng-template to toggle between edit/view mode.
<tr *ngFor="let emp of EMPLOYEES;let i=idx">
<ng-template [ngTemplateOutlet]="loadTemplate(emp)" [ngOutletContext]="{ $implicit: emp, idx: i }"></ng-template>
</tr>
On the article he uses template driven forms to edit the row. However, I was trying to change to reactive forms.
In my attempt to do that, I tried to replace the [(ngModel)] to formControlName and I got some errors. My first attempt I tried to add the [formGroup] at the beginning of the template html inside form element. But when I tried to run and edit the row, I got the following error:
Error: formControlName must be used with a parent formGroup directive. You'll want to add a formGroup directive and pass it an existing FormGroup instance (you can create one in your class).
When I tried to move the [formGroup] inside the ng-template it works, however I was not able to bind the value to the fields and I had to set the values in the loadTemplate function:
loadTemplate(emp: Employee) {
if (this.selemp && this.selemp.id === emp.id) {
this.rForm.setValue({
id: emp.id,
name: emp.name
});
return this.editTemplate;
} else {
return this.readOnlyTemplate;
}
}
This works and show the values inside the fields in a read only mode :(
Here is the Plunker of what I have got so far.
How can I make a reactive form work with ng-template and how to set values to edit the entries?
Any help is appreciated! Thanks
Actually your form is not readonly, you are just constantly overwriting the input you are entering. Since you are having a method call in template (which is usually not a good idea), loadTemplate gets called whenever changes happen, which in it's turn means that
this.rForm.setValue({
id: emp.id,
name: emp.name
});
gets called over and over whenever you try and type anything. We can overcome this with instead setting the form values when you click to edit. Here we also store the index so that we can use it to set the modified values in the correct place in array, utilizing the index could perhaps be done in a smarter way, but this is a quick solution to achieve what we want.
editEmployee(emp: Employee) {
this.index = this.EMPLOYEES.indexOf(emp)
this.selemp = emp;
this.rForm.setValue({
id: emp.id,
name: emp.name
});
}
so when we click save, we use that index...
saveEmp(formValues) {
this.EMPLOYEES[this.index] = formValues;
this.selemp = null;
this.rForm.setValue({
id: '',
name: ''
});
}
Your plunker: https://plnkr.co/edit/6QyPmqsbUd6gzi2RhgPp?p=preview
BUT notice...
I would suggest you perhaps rethink this idea, having the method loadTemplate in template, will cause this method to fire way too much. You can see in the plunker, where we console log fired! whenever it is fired, so it is a lot! Depending on the case, this can cause serious performance issues, so keep that in mind :)
PS. Made some other changes to code for adding a new employee to work properly (not relevant to question)

AngularJS doesn't update two-way bound model used in custom directive when model is changed from code called by that directive

You'll have to bear with me on this one, because it's a little involved and isn't answered by the following questions:
Two way binding Angularjs directives isn't working
AngularJS: Parent scope is not updated in directive (with isolated scope) two way binding
Both of those answers are more than 3 years old and no longer apply. Please do not mark this as a duplicate of those questions.
I wish that I could make a simple, reproducible version of this problem, but my Angular is honestly not good enough, and it is a fairly complex setup with lots of meddling from the outside. But the title is correct.
TL;DR version:
User presses escape from input box made by custom directive
Code in parent scope is called through ngKeyup in order to change the value, by updating the bound model
This update is not reflected in the data displayed by the directive
Putting code to do that update inside the directive itself, which directly updates its OWN bound model, to escape the problem of parent-scope bound-model changes not working, does not work.
In fact, even though it is changed inside its own directive controller, it sets the parent-scope model and its own model to the input value instead of the one I'm trying to overwrite with.
And it still doesn't work.
???
Long version with code:
I had a bunch of code I was repeating over and over again in my HTML, and decided to make a custom directive. The code uses many functions from the parent scope. You don't need to know what all these functions do, just note there's a ton of them that happen on click, on blur, etc. So, it looks like this:
template html file
<div ng-if="should_display" class="myclass" ng-class="cssSizeClass">
<div ng-if="allow_edits">
<span
class="text myapp-edit-hover"
ng-click="showEditField = true; captureValue(propertyString,binValue, $event)"
ng-hide="showEditField"
{{ binValue | customNumber: numberPlaceholder }}{{ numberType }}
</span>
<input
ng-show="showEditField"
class="myapp-editable-input"
type="text"
focus-on-show
ng-focus="selectText($event)"
ng-model="binValue"
ui-number-mask="numberPlaceholder"
ui-hide-group-sep
ng-blur="showEditField = false; inputUpdate(propertyString, binValue, 27, false )"
ng-keyup="inputUpdate(propertyString, binValue, $event, false)">
<span class="numberSuffix">{{ numberSuffix }}</span>
</div>
In my directive controller, in order to use those pesky parent-scope functions...
.directive('appDetailCardInput', function() {
return {
restrict: 'E',
templateUrl: '/static/js/directives/mytemplate.html',
scope: { // # = string, = = object, & = function
propertyName: '#',
cssSizeClass: '#',
numberPlaceholder: '#',
numberType: '#',
binValue: '=',
editable: '=',
numberSuffix: '#'
},
controller: function($scope) {
// define local properties
$scope.should_display = $scope.$parent.thing;
$scope.propertyString = 'thing.' + $scope.propertyName;
// HERE, outside functions are "captured"
$scope.captureValue = $scope.$parent.captureValue;
$scope.inputUpdate = $scope.$parent.inputUpdate;
$scope.selectText = $scope.$parent.selectText;
// check if field is editable
if (typeof $scope.editable === 'undefined') {
$scope.allow_edits = true;
}
else {
$scope.allow_edits = $scope.editable;
}
}}
};
});
This actually works 90%! When the bound model in the parent scope is updated outside the Angular digest cycle (the usual case is during a callback from an async messaging service), it updates the displayed value properly. Other things that occur "outside" the directive update it properly.
However. When escape is pressed or a blur happens, or any code is called that updates the value from "inside" the custom directive, no such luck.
I'd like to take a moment here to note that the $event target will blur properly when programmatically told to.
In the case of the escape key, the inputUpdate() function is supposed to set the value back as if someone hadn't typed something in the input. The problem comes here:
// (in inputUpdate function)
$event.target.blur(); // This works
$event.target.value = $scope.previouslyCapturedValue; // This works
setstr = "$scope.thing." + ser + " = " + $scope.previouslyCapturedValue + ";"; // This does not work
eval(setstr);
The eval() changes the parent scope model back, but that isn't reflected in the child scope.
So I tried both methods from the answers above. Adding a $watch in a link function only fires the SECOND time escape is pressed, not the first, so the first time someone hits escape on a field, it doesn't help! Fail.
The Angular docs suggest wrapping the changes to the value in a $timeout, so that the changes are applied outside of the digest cycle. Doing so doesn't help...
$timeout(function() {
console.log('evaluating outside digest supposedly');
$scope.nexttarget = $scope.nextval;
eval($scope.nextstr);
}, 0, false);
Nothing changes, the same behavior happens. The displayed value does not change (although the value that you change when clicking on it does).
So with some more digging, I find out that $event.target.value = $scope.previouslyCapturedValue; is just setting the value of the input, and not updating the actual bound model. That's fine. The bound model just needs to be updated too.
If I could somehow directly overwrite the text displayed on the , that would almost be a worthwhile solution compared to going back to how things were before.
But like I said before, when it is updated exactly the same as eval($scope.nextstr); from an external messaging service, the update happens perfectly correctly.
So at this point in the story I get it into my head: why not 'intercept' the ngKeyup directive and set the model right there inside the directive? I put this in my controller:
$scope.inputUpdate = function(ser, value, $event, dropdown) {
var keyCode = $event.keyCode;
console.log('got inputupdate!');
if ((parseInt(keyCode) == 27)
|| (typeof keyCode === 'undefined')) {
console.log('escape or deblur!');
if (ser !== $scope.dontBlur) {
console.log('going to set it!!!')
$scope.binValue = $scope.$parent.previouslyCapturedValue; // This is the correct value confirmed by console logs
}
}
$scope.$parent.inputUpdate(ser, value, $event, dropdown);
}
Nope! It gets there in code, but it still doesn't display the correct value! In fact, the bound model is updated to what had been typed in, even though it had been programmatically changed back right there!
Why does it update fine from outside its own code from another controller, but NOT from its own code, even if it's being done directly in the directive controller itself in the "intercepted" function?
Can't wrap my head around this. It's like the bound model is "locked" during this time period or something.
Thanks for any guidance.

Angular Component: how to specified value for output binding function defined in parent?

I wanted to use Angular 1.5's component to get benefit from its one-way binding: <hero-detail hero="ctrl.hero" save="ctrl.save(hero)"></hero-detail>. But, as Todd Motto points on his blog: Todd Motto's blog: One-way data-binding in Angular 1.5, it works properly only for primitives. So I had to bind primitives:
<hero-detail
name="ctrl.hero.name"
id="ctrl.hero.id"
save="ctrl.save(ctrl.hero)"
></hero-detail>
And next in component, on $onInit hook, make hero object from primitives:
HeroDetailController.prototype.$onInit = function(){
this.hero = {
name: this.name,
id: this.id
}
console.log('OnInit called');
}
And call specified function when user clicks save. Weird part is, that if user changes hero's name inside component and clicks save, when function bound from parent is called, it does not have changes from hero-detail component. I made a plunker which shows my problem: Plunk which shows problem with children/parent output binding in Angular 1.5 - if you open Developer Console, click "Set name..." and then click save, you will see console.logs which will show you that from hero-detail it is Spawn2, but in parent context (where should be logic, like talking to $http service), it has still old value Spawn. Am I missing something?
Code from Angular docs looks pretty like my code:
<button ng-click="$ctrl.onDelete({hero: $ctrl.hero})">Delete</button>
I have no clue what's going on. Thank you in advance for helping me to deal with this problem.
P.S. I had some problem with Plunk versions, now everything is OK - in Developer Console in your browser you can see problems with updates
To avoid confusion about the scope of variables (parent or child), prefix injected variables with $.
/* WAS
<hero-detail
name="ctrl.hero.name"
id="ctrl.hero.id"
save="ctrl.save(ctrl.hero)"
></hero-detail>
*/
//SHOULD BE
<hero-detail
name="ctrl.hero.name"
id="ctrl.hero.id"
save="ctrl.save($hero)"
></hero-detail>
Then in your code:
HeroDetailController.prototype.saveHero = function(hero) {
console.log('Hero name from child: ' + hero.name);
this.save({
$hero: hero
});
};
This way you can tell which variables of the expression are from the parent scope and which variables are from the directives scope.

Add AngularJS directive at runtime

I am creating a game where the first thing that needs to happen is some state is loaded in from an external JSON file - the contents of one of my directives are dependent on this data being available - because of this, I would like to delay applying the directive until after the data has loaded. I have written the following:
window.addEventListener('mythdataLoaded', function (e) {
// Don't try to create characters until mythdata has loaded
quest.directive('character', function() {
return {
restrict: 'A',
scope: {
character: '#'
},
controller: 'CharacterCtrl',
templateUrl: 'partials/character.html',
replace: true,
link: function(scope, element) {
$(document).on('click', '#'+scope.character, function () {
$('#'+scope.character+'-popup').fadeToggle();
});
}
};
});
});
// Load in myth data
var myth_data;
$.getJSON("js/mythdata_playtest.json", function(json) {
myth_data = json;
window.dispatchEvent(new Event('mythdataLoaded'));
});
However, it appears that my directive's link function never runs - I'm thinking this is because angular has already executed the part of it's cycle where directives are compiled/linked by the time this directive gets added. Is there some way to force angular to compile this directive after it is created? I googled around a bit, and some people suggested adding $compile to the link function for similar issues - but the link function is never run, so that doesn't work for this case. Thanks!
It seems to me it would be better to always configure the directive, to do the JSON call in the directive, and attach logic to the element in the JSON call's success handler. This would, if I understand you correctly, do what you want.
AngularJS is meant as a framework, not a library, so using it in the way you mentioned is not recommended. Exactly as you mentioned, AngularJS does a lot of things for you when it runs. AngularJS, by default, runs on document loaded, and your $.getJSON callback arrives after that. When AngularJS runs it does all its magic with compiling the content and all that.
As a sidenote, it's also more the Angular way to use $http over $.getJSON.
I think you're thinking about this the wrong way. A major ideology in angular is that you set up declarative elements and let it react to the state of the scope.
What I think you might want to do is pass in what you need through the directive scope, and use other angular built in directives to hide or show your default ("non directive") state until the scope gets set from the controller for example.
Example:
You want a box to be hidden until an api call comes back. Your directive sets special styles on your element (not hidden). Instead of delaying to dynamically set your directive, you can pass in a scope var with a default value and use something like ng-show="data.ready" in your directive template to handle the actual dom stuff.

Failing to understand data binding in angularjs

I have a jsbin setup here http://jsbin.com/esofiq/1/edit
I'm getting confused by the way I think angularjs should work, I have some json data attached to a data attribute, angularjs fetches the data and creates the view. Doesn't calling $scope.mydata within the controller set 'mydata' as the model, and shouldn't it now update the view if the data within the data attribute is changed?
Is this easier to achieve in other frameworks if this isn't appropriate for angular?
I think these two will give you the idea:
How Angular uses data
How to make AJAX calls
Although this is not the usual way to do things in Angular, you can achieve what you want, adding a watch to your data
$scope.$watch(
function () { return $("#mydata").data("a");},
function(newValue) {
$scope.mydata = newValue;
}, true);
Basically we are adding a change listener to your data.
Please check this plunker, where the jquery data is changed every 2 seconds, and the div reacts to this change.

Categories

Resources