View (HTML)
<p>First name: <input data-bind="value: firstName" /></p>
<p>Last name: <input data-bind="value: lastName" /></p>
<h2><span data-bind="text: fullName"> </span>!</h2>
nameConcat.js (KnockoutJS)
var ViewModel = function(first, last) {
this.firstName = ko.observable(first);
this.lastName = ko.observable(last);
this.fullName = ko.pureComputed(function() {
return this.firstName() + " " + this.lastName();
}, this);
};
ko.applyBindings(new ViewModel("Tom", "Haykens"));
React Component
class NameConcat extends React.Component {
render() {
return (
???
);
}
}
I am a ReactJS newbie. How do I display knockoutJS application pages from ReactJS components ? Rewriting KnockoutJS pages in ReactJS is not an option.
Thanks in advance.
In my project the skeleton was designed in Durandel ,part of the reason I could not use the option of rewriting in React .
So here is what I had to use :
Case 1) - Want to bind a data in KO way
public observableVariable = Observable();
public binding() { // durandle lifecycle hook // observable declared with the class
reactDom.render(<span data-bind={`text: observableVariable `}></span>, document.getElementById('reactRoot'));
}
Case 2) - Want to bind an already KO page to React and leave the DOM to be updated at real DOM .
public observableVariable = Observable();
public binding() { // durandle lifecycle hook // observable declared with the class
reactDom.render(<div data-bind={`compose: { model: '-- index page where the knockout binding is written --',activationData: { --Observable parameter--: --Observable data--} }`}></div>, document.getElementById('reactRoot'));
}
Case 3) - Template Binding
// declare all the observable at the top
// All These component can be sent anywhere from the react composed child
<div dangerouslySetInnerHTML={{
__html: `<!--ko widget: {
kind: '--index--',
name: 'sample comp',
// write all the observable dependencies here ,
id: 'sample Id'
} -->
<!-- /ko -->`}} />
Case 4) If you want to load regular react component using observable : I mean if you want some data maintained in observable to use in react.
Have an tracking object and keep the data in sync using Observable subscribe :
{
self reactPropValue;
self.selectedVals.subscribe(function (newValue: any) {
let val = ko.toJSON(newValue);
reactPropValue= ko.toJSON(selectedVals());
});
}
Now you can pass this into react component as a prop and each time the observable changes the subscribe will make sure to get a new value , dont forget to wrap your reactDom in side an function and call on subscribe ,because by design it wont trigger a reload .
If you are not in durandel you might need to add the ko.applybinding yourself otherwise its fine .
Look out for the error message in the console window if you get any , There could be some .
Sorry if my answer is not properly formatted .
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/
My scenario as follows
1) When the user enters a keyword in a text field and clicks on the search icon it will initiate an HTTP request to get the data.
2)Data is rendered in HTML with ngFor
The problem is on the first click the data is not rendered in HTML but I am getting the HTTP response properly, and the data rendered only on second click.
component.ts
export class CommerceComponent implements OnInit {
private dealList = [];
//trigger on search icon click
startSearch(){
//http service call
this.getDeals();
}
getDeals(){
this.gatewayService.searchDeals(this.searchParams).subscribe(
(data:any)=>{
this.dealList = data.result;
console.log("Deal list",this.dealList);
},
(error)=>{
console.log("Error getting deal list",error);
this.dealList = [];
alert('No deals found');
}
);
}
}
Service.ts
searchDeals(data){
var fd = new FormData();
fd.append('token',this.cookieService.get('token'));
fd.append('search',data.keyword);
return this.http.post(config.url+'hyperledger/queryByParams',fd);
}
HTML
//this list render only on second click
<div class="deal1" *ngFor="let deal of dealList">
{{deal}}
</div>
UPDATE
click bind html code
<div class="search-input">
<input type="text" [(ngModel)]="searchParams.keyword" class="search" placeholder="" autofocus>
<div class="search-icon" (click)="startSearch()">
<img src="assets/images/search.png">
</div>
</div>
According to Angular official tutorial, you could have problems if you bind a private property to a template:
Angular only binds to public component properties.
Probably, setting the property dealList to public will solve the problem.
Remove "private" from your dealList variable. That declaration makes your component variable available only during compile time.
Another problem: you are implementing OnInit in yout component but you are not using ngOnInit. Angular is suposed to throw an error in this situation.
My suggestion is to switch to observable:
I marked my changes with CHANGE
component.ts
// CHANGE
import { Observable } from 'rxjs/Observable';
// MISSING IMPORT
import { of } from 'rxjs/observable/of';
export class CommerceComponent implements OnInit {
// CHANGE
private dealList: Observable<any[]>; // you should replace any with your object type, eg. string, User or whatever
//trigger on search icon click
startSearch() {
//http service call
this.getDeals();
}
getDeals() {
this.gatewayService.searchDeals(this.searchParams).subscribe(
(data:any)=>{
// CHANGE
this.dealList = of(data.result);
console.log("Deal list",this.dealList);
},
(error)=>{
console.log("Error getting deal list",error);
// CHANGE
this.dealList = of([]);
alert('No deals found');
}
);
}
}
HTML
<!-- CHANGE -->
<div class="deal1" *ngFor="let (deal | async) of dealList">
{{deal}}
</div>
Try this:
this.dealList = Object.assign({},data.result);
Better do this inside the service.
By default, the angular engine renders the view only when it recognizes a change in data.
I have a code:
document.getElementById('loginInput').value = '123';
But while compiling the code I receive following error:
Property value does not exist on type HTMLElement.
I have declared a var: value: string;.
How can I avoid this error?
Thank you.
if you want to set value than you can do the same in some function on click or on some event fire.
also you can get value using ViewChild using local variable like this
<input type='text' id='loginInput' #abc/>
and get value like this
this.abc.nativeElement.value
here is working example
Update
okay got it , you have to use ngAfterViewInit method of angualr2 for the same like this
ngAfterViewInit(){
document.getElementById('loginInput').value = '123344565';
}
ngAfterViewInit will not throw any error because it will render after template loading
(<HTMLInputElement>document.getElementById('loginInput')).value = '123';
Angular cannot take HTML elements directly thereby you need to specify the element type by binding the above generic to it.
UPDATE::
This can also be done using ViewChild with #localvariable as shown here, as mentioned in here
<textarea #someVar id="tasknote"
name="tasknote"
[(ngModel)]="taskNote"
placeholder="{{ notePlaceholder }}"
style="background-color: pink"
(blur)="updateNote() ; noteEditMode = false " (click)="noteEditMode = false"> {{ todo.note }}
</textarea>
import {ElementRef,Renderer2} from '#angular/core';
#ViewChild('someVar') el:ElementRef;
constructor(private rd: Renderer2) {}
ngAfterViewInit() {
console.log(this.rd);
this.el.nativeElement.focus(); //<<<=====same as oldest way
}
A different approach, i.e: You could just do it 'the Angular way' and use ngModel and skip document.getElementById('loginInput').value = '123'; altogether. Instead:
<input type="text" [(ngModel)]="username"/>
<input type="text" [(ngModel)]="password"/>
and in your component you give these values:
username: 'whatever'
password: 'whatever'
this will preset the username and password upon navigating to page.
Complate Angular Way ( Set/Get value by Id ):
// In Html tag
<button (click) ="setValue()">Set Value</button>
<input type="text" #userNameId />
// In component .ts File
export class testUserClass {
#ViewChild('userNameId') userNameId: ElementRef;
ngAfterViewInit(){
console.log(this.userNameId.nativeElement.value );
}
setValue(){
this.userNameId.nativeElement.value = "Sample user Name";
}
}
In My application i have a template file with 2 fields(say name1 / name2).
Based on one parameter("preference") in the route i want to display either Recipe1 or Recipe2 on the screen.
For eg: if preference is veg, i should display Recipe1 else Recipe2.
i tried this as below but it did not work.
export default Ember.Route.extend({
beforeModel(){
if (preference==='veg'){
console.log("Inside Veg..");
Ember.$("#Recipe2").attr("type","hidden");
}
else {
console.log("Inside Non Veg..");
Ember.$("#Recipe1").attr("type","hidden");
}
What i see is that it goes inside the if/else loop but the ember.$ statements dont make any difference.Please help.
First of all you should not write Ember.$ inside beforeModel hook. that's wrong. When beforeModel hook called, DOM will not be ready. I prefer you to create component and pass preference property to component and have if check to display it in hbs
Create my-receipe component and include it in template.hbs
{{my-receipe preference=preference }}
my-receipe.hbs
{{#if isVeg}}
<input type="text" id="Recipe1" />
{{else}}
<input type="text" id="Recipe2" />
{{/if}}
my-receipe.js
Create isVeg computed property which will return true if the preference is veg.
export default Ember.Component.extend({
isVeg: Ember.computed('preference', function() {
return Ember.isEqual(this.get('preference'), 'veg');
})
})
I know this is somewhat duplicated, but All my efforts to create a dynamic component renderer are failing possibly due to my lack of knowledge in ember concepts.
My Scenario is a multi purpose search bar which will search for models in the cache. I would like each search result to be rendered below the search input according to the model's type key. the handlebars file will be named according to the model type with the syntax components/app-search-<model-type-key>.hbs e.g. the template name for a customer model should be components/app-search-customer.hbs
my search template looks like this:
<div class="well well-md">
<div class="input-group">
{{input value=searchTerm class="form-control"}}
</div>
{{#if searchTerm}} <!-- Updating searchTerm causes results to populate with models -->
{{#if results.length}}
<ul class="list-group search-results">
{{#each result in results}}
<!-- todo -->
{{renderSearchComponent model=result}}
{{/each}}
</ul>
{{else}}
Nothing here Captain
{{/if}}
{{/if}}
</div>
And my attempt at a renderSearchComponent helper looks like this:
Ember.Handlebars.registerHelper('renderSearchComponent', function(model, options) {
var modelType = options.model.constructor.typeKey,
componentPath,
component,
helper;
if (typeof modelType === 'undefined') {
componentPath = "app-search-default";
} else {
componentPath = "app-search-" + modelType;
}
component = Ember.Handlebars.get(this, componentPath, options),
helper = Ember.Handlebars.resolveHelper(options.data.view.container, component);
helper.call(this, options);
});
When this runs the options.model throws: TypeError: options.model is undefined and additionally i have the following error:
Error: Assertion Failed: Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications.
I have been batting my eyelids for what seems hours now trying to get this right. Is what I am asking for even possible?
Thank you in advance.
I know this is a year old question, but Ember since version 1.11+ has the new component helper to dynamically render components.
{{#each model as |post|}}
{{!-- either foo-component or bar-component --}}
{{component post.componentName post=post}}
{{/each}}
You were on the right path, a working example could be like:
import {handlebarsGet} from "ember-handlebars/ext";
Ember.Handlebars.registerHelper('renderSearchComponent', function(value, options) {
var propertyValue;
if (options) {
if ( options.types[0] !== 'STRING' ) {
var context = (options.contexts && options.contexts.length) ? options.contexts[0] : this;
propertyValue = handlebarsGet(context, value, options);
propertyValue = propertyValue.constructor.typeKey;
} else {
propertyValue = value;
}
} else {
options = value;
propertyValue = 'default';
}
var property = 'app-search-'+propertyValue;
var helper = Ember.Handlebars.resolveHelper(options.data.view.container, property);
if (helper) {
return helper.call(this, options);
}
});
This helper allows to pass either string, nothing or binding property.
{{renderSearchComponent}}
{{renderSearchComponent 'book'}}
{{renderSearchComponent result}}
Helper internals are not completed documented, i think because they are not a public API. But you could take inspiration by looking at the helper source code.