Interact with object passed to Angular2 component as Input - javascript

(to save some reading -- the problem boiled down to a missing 'this' when referencing the #Input'ed variables)
I have a parent class - this will (eventually) display a list of "QuestionComponents".
The relevant part of the parent class template is as follows:
<question [(justText)]="testText" [(justObj)]="testObj" [(myQuestion)]="singleQuestion"></question>
Suffice it to say I'm getting the "singleQuestion" object from a http service call. the Question class, of which "singleQuestion" is an instance, has a basic "toString" method for easier output debugging.
testText, textObj, and singleQuestion are all just objects of increasing complexity for me as I tested. For now it's very basic and only has one "question" as I work on my understanding.
The child "QuestionComponent" looks like this:
#Component ({
selector:
'question',
templateUrl:
'./app/components/question/question.component.html',
directives: [FORM_DIRECTIVES],
})
export class QuestionComponent {
#Input() myQuestion:USGSQuestion
#Input() justText:string
#Input() justObj:object
constructor() {
}
onClick() {
myQuestion.generalInfo.label = "changed";
console.log("change attempted");
}
}
Note that as time moves on, I'll need to be able to do some heavy interaction with the objects that are passed into the QuestionComponent. In fact, my preference was to just pass the Question object into the constructor, but passing data into the component constructor doesn't seem to work for some reason. (I digress) To attempt to check how I can interact with passed data , I built the onClick button and tried to change something in one of the #Input'ed variables.
The template for the child object is as so:
<div *ngIf="!justText">
no text passed
</div>
<div *ngIf="justText">
<b>Here is the passed Text:</b><br>
{{justText}} <br>
</div>
<br>
<div *ngIf="!justObj">
no obj passed
</div>
<div *ngIf="justObj">
<b>Here is the passed JSON:</b><br>
Foo: {{justObj.foo}} <br>
Baz: {{justObj.baz}}
</div>
<br>
<div class="question">
<i>I am a question spot</i>
<div *ngIf="!myQuestion">
Loading Question...
</div>
<div *ngIf="myQuestion">
<b>Here is the passed question:</b><br>
{{myQuestion}} <br>
</div>
</div>
<button (click)="onClick()">Clickit</button>
How do I get a reference to the #Input'ed objects inside the class? (in this case, how would I modify "myQuestion" in the 'onClick()' function? ... and, secondarily, how would I ensure changes to the objects got passed to the view and updated?
I should note I already tried to pass the value from the 'view' back through the onClick call, like so:
(change button to:)
<button (click)="onClick(myQuestion)">Clickit</button>
(and change onClick to:)
onClick(q) { q.generalInfo.label = "changed"; }
This did not work.

I'm an idiot.
After a few hours of research and searching (Before asking) it all came down to adding "this".
... as in "myQuestion.generalInfo.label = "changed";" should have been "this.myQuestion.generalInfo.label = "changed";"
Some days you gotta love programming, eh?

Related

How to use nativeElement.focus() with each unique input inside ngFor in angular

I have a ngFor loop as below.
<div *ngFor= "let transporter of transporters">
<input type="text" [(ngModel)]= "transporter.name" #inputTransporter *ngIf="openTransporterFreightEditing">
<mat-icon class="cursor-btn" (click)="openEdit()">mode_edit</mat-icon>
</div>
And the TS code:
openTransporterFreightEditing:boolean = false
#ViewChild('inputTransporter') inputTransporter: ElementRef;
openEdit() {
this.openTransporterFreightEditing = true
this.inputTransporter.nativeElement.focus()
}
The input field is at first hidden. When the edit symbol is clicked the input becomes visible. I want to focus the input as soon as it becomes visible. However,I want to focus on one particular input by using nativeElement.focus(), but I am facing difficulty because of the same ID "inputTransporter" in each input element created by the loop.
How do I focus one particular input ID based on (click). If I have to generate unique ID for each element I may have to use [attr.id] which isn't working with #ViewChild .
My code in stackblitz :
https://stackblitz.com/edit/angular-4hedvr?file=src%2Fapp%2Fapp.component.ts
You may use #ViewChildren to access an array of QueryList.
You may pass the index to your openEdit function and access the required native element and then focus it.
For a demo sample on how to access the children, please refer to the below link:-
https://stackblitz.com/edit/angular-sgudix?file=src%2Fapp%2Fapp.component.ts
app.component.html
<div>
<div *ngFor="let a of [1,2,3]; let i=index">
<p #para>Para {{a}}</p>
<button (click)="colorMe(i)">Color Me</button>
</div>
</div>
app.component.ts
#ViewChildren('para') paras: any;
paraElements: any;
ngAfterViewInit() {
this.paraElements = this.paras.map(para => {
console.log('Paras: ', para.nativeElement);
return para.nativeElement;
})
}
colorMe(index) {
console.log('Index: ', index);
this.paraElements[index].style.backgroundColor = '#5789D8';
}
This should give you a good idea on how to implement it at your end.
Hope it helps.
PS: Somethings are not as per standard like declaring variables inside HTML, using any type, etc. as it was a rough demo application to just give an idea.

child not updating based on props in Vue.js

I have something like this in my parent scope:
<form-error :errors="errors"></form-error>
<textarea class="form-control" name="post" cols="30" rows="8" #keydown="errors.clear('post')" v-model="post"></textarea>
Note the #keydown event, where I am clearing out the errors, by calling
method on a class.
With :errors="errors" I am passing instance of the below Errors class,
into <form-error> child component:
class Errors {
constructor() {
this.errors = {};
}
get(field) {
if (this.errors[field]) {
return this.errors[field][0];
}
}
clear(field) {
delete this.errors[field];
}
has(field) {
return this.errors.hasOwnProperty(field);
}
}
And in <form-error> child component I have this:
<template>
<div v-if="errors.has('post')" class="alert alert-danger" v-text="errors.get('post')"></div>
</template>
<script>
export default {
props: ['errors']
};
</script>
Now, while v-text="errors.get('post')" works fine, and I am getting error
displayed, the v-if="errors.has('post')" part doesn't work at all.
I am assuming errors is passed the right way as props, otherwise that
errors.get('post') wouldn't work.
Question is, why when parent triggers that #keydown event, and I see the
errors object is being emptied properly (Vue addon for chrome), the v-if
part doesn't update, thus hiding the div?
As you can see, the <FormError> child component is being updated to reflect the change in errors when I start typing, but still v-if doesn't trigger.
Edit
What's even more confusing, docs say:
Note that objects and arrays in JavaScript are passed by reference, so if the
prop is an array or object(as in my case), mutating the object or array itself inside the
child will affect parent state.
Although of course I am not mutating the object from with in my child, but the
important part is that object changes in parent should be reflected in child.
Are you getting any error. As I can see Map.has has poor support in browsers. You can try using any of following alternatives:
post in errors
<template>
<div v-if="'post' in errors" class="alert alert-danger" v-text="errors.get('post')"></div>
</template>
errors['post']
<template>
<div v-if="errors['post'] !== undefined" class="alert alert-danger" v-text="errors.get('post')"></div>
</template>

Aurelia Custom Elements data binding

I am using a custom-element with my Aurelia app. When I am binding data from my view-model with the custom-element, it is working fine. Even if I make some changes in the data in the custom-element control, the change is also reflected to the data in my view model, thanks to the two-way data binding.
However, if I make some changes in the data from the view model (using javascript), the data is not updated in the custom-element. I have replicated this problem for a simpler setting.
Simple View Model
export class Playground {
public testObj: any;
counter = 0;
constructor() {
this.testObj = {
prop1: "This is prop1 value"
, collection2: [{ id: "item21" }]
}
}
updateProp1() {
alert("before update: "+this.testObj.prop1);
this.testObj.prop1 = "Sayan " + this.counter++;
alert(this.testObj.prop1);
}
verifyChange() {
alert(this.testObj.prop1);
}
}
Simple View
<template>
<h1>
Playground
</h1>
<div >
<div repeat.for="item of testObj.collection2">
<div class="row">
<div class="col-sm-4">
<input type="text" class="form-control" placeholder="prop1"
value.bind="$parent.testObj['prop1']">
</div>
</div>
</div>
<button click.delegate="updateProp1()" class="btn btn-primary"> Update Prop1 </button>
<button click.delegate="verifyChange()" class="btn btn-primary"> Verify Change </button>
</div>
</template>
Now whenever I click Verify Change after changing the value in textbox, the changed value comes in the alert. But, if I click the Update Prop1 button, the prop1 value gets updated, but the change doesn't reflect in the view.
I am not sure exactly what I am missing.
Note: Instead of using $parent.testObj['prop1'], if $parent.testObj.prop1 is used, the databinding works as it should. However, my actual custom-element is of generic kind and the property name is not known before hand, hence it seems that I need to keep using $parent.testObj['prop1'] notation instead of dot notation: $parent.testObj.prop1.
At pointer/suggestion/feedback is appreciated.
Update: If anyone can point out the the difference between the dot notation and indexer notation w.r.t. aurelia data-binding (I have checked this already), that will be of great help.
This was an issue in earlier builds of the aurelia/binding module. Here's the related issue(s):
https://github.com/aurelia/binding/issues/75
https://github.com/aurelia/binding/pull/76
I tried your view/view-model in the latest version of aurelia and everything worked. Here's a screencast of what I saw: http://screencast.com/t/KqcuqXWjkU2
Make sure you have the latest version of the aurelia components installed- update steps here: http://blog.durandal.io/2015/05/01/aurelia-may-status-and-releases/

ng-click doesn't take parameters from the DOM

I have the following code:
<input id="id">
<button data-action="bea" ng-click="Create($('#id1')[0].value);" class="btn">Insert ID</button>
<button data-action="bea" ng-click="Create($('#id2')[0].value);" class="btn">Insert ID</button>
In the JS I have:
$scope.Create = function (id){
if (id === undefined) {
$scope.data = "You must specify an id";
} else {
$scope.data = data;
console.log(data);
});
}
};
When the call gets into the Create function the value of the id is undefined.
If I add the following line at the beginging of the Create function everything works ok:
id = $('#id')[0].value;
If I send a constant value it works:
<button data-action="bea" ng-click="Create('SomeID');" class="btn">Insert ID</button>
Why is this happening and how can I do that without putting the line of value into the method?
Thanks
This is just an extension of comments and other answers, You could achieve this in many ways using angular, one simple example could be:-
<!-- Add a controller -->
<div ng-controller="MainCtrl">
<!-- Give a model binding to your text input -->
<input ng-model="userEntry" type="text"/>
<!-- ng-click pass which ever argument you need to pass, provided it is an expression that can be evaluated against the scope or any constants -->
<button data-action="bea" ng-click="Create(userEntry);" class="btn">Insert ID</button>
<!-- Some simple data binding using interpolation -->
{{data}}
<!-- Just for demo on repeater on a list of items on the scope -->
<div ng-repeat="item in items track by $index">{{item}}</div>
</div>
Example Demo
My 2 cents on the lines of what were originally trying to do:-
Use angular bindings instead of accessing DOM directly for getting the data, it really helps you deal with just the data without worrying about how to access or render it in DOM. If you think you need to access DOM for implementing business logic re-think on the design, if you really need to do it, do it in a directive. Angular is very opinionated on the design and when where you do DOM access.
ng-model
ng-binding
controller
all about ngmodel controller
This is not the way you should do in AngularJS. You should really think in Angular if you want to use AngularJS. Refer this post ("Thinking in AngularJS" if I have a jQuery background?)
All DOM manipulation should be done in Directive. Refer this page that I found really clear.
(http://ng-learn.org/2014/01/Dom-Manipulations/)
My guess is that $ is not bound to the jQuery function when the ng-click value is evaluated, because it is not exposed in the Angular scope.
Solutions to adress this:
expose the jQuery function in scope somewhere, e.g $scope.$ = $; in a controller.
make the Create function parameterless as you suggested, with a var id = $('#id')[0].value; at the beginning
my favorite : avoid using jQuery. If you put some data in the #id element, there's probably a more natural and AngularJS-idiomatic way of retrieving it than querying the DOM (e.g an Angular service).
In particular, if the element you're targeting is an <input> element, then use the ngModel directive to link the value to a $scopeproperty that will be accessible in the controller :
<input ng-model="inputData"/>
The JavaScript you are trying to pass as a parameter of the create function is not available in the scope of the Create function.
Try to target the element a different way.
Does that help?

Data Binding between pages in Angularjs

I am trying to understand data binding in Angularjs.
What I want to do is establish binding between pages that is if I change the input on first.html, the data should automatically change in second.html.
For example,
This is first.html:
<div ng-controller="MyCtrl">
<input type="text" ng-model="value"/><br>
{{value}}
<input type="submit" value="Second page"/>
</div>
and say second.html has only this piece of code {{value}}.
and in the .js file we have $routeProvider which takes the template url as 'second.html' & the controller is 'MyCtrl'.
So the controller is:
MyApp.controller(function($scope){
$scope.value="somevalue";
})
By doing the above way the {{value}} on the second.html is getting the value "somevalue". Which is comming from the controller.
But if I change the input value dynamically that is on the first.html, the value on the second.html is not getting that value.
My question is how do I bind the value on second.html with first.html automatically.
To understand the question clearly, Suppose there is an input field for entering text and a submit button on first.html, then I want to get the Input value of the text field of the first.html on the second.html page on Submit.
Use a service and store your model there. Gloopy already has a good example of this here: https://stackoverflow.com/a/12009408/215945
Be sure to use an object property instead of a primitive type.
If you'd rather use $rootScope, then as above, define an object, rather than a primitive:
$rootScope.obj = { prop1: "somevalue" }`
then bind to that object property in your views:
<input type="text" ng-model="obj.prop1">
{{obj.prop1}}
If you attach your data to $rootScope if will survive transitions across controllers and be part of all $scopes (prototype inheritance magic)
//**attach in controller1:**
function MyCtrl1($rootScope) {
$rootScope.recs= { rec1 : "think philosophically" };
}
//**do nothing in controller for view2:**
function MyCtrl2($scope) {
//nothing
}
//**Markup for view2: automaticall makes use of data in $routeScope**
<p>Try doing this: {{recs.rec1 }}</p>
//**markup for view1 to respond to OPs question in comments**:
<input ng-model="recs.rec1" />
Update: Creating a custom service is a more scalable and structurally sound way to handle this, but the $rootScope method is the quick and dirty way.
Update2: added view1 markup to respond to OP question, edited example to take advantage of correct advice to use object rather than primitive.
Found the Solution to what I was looking for, The solution is in the Angular docs, here is the link http://docs.angularjs.org/cookbook/deeplinking.
Some part of the example on that link answers my question.
You should user $broadcast, $emit or scope communication. Try to avoid overloading the rootScope. It is as a bad practice as saving data into the application sessions.

Categories

Resources