So I'm trying to build a dynamic form in Angular2 (and NativeScript)..
What i've done so far is:
Get JSON from REST API
Add Component (via a template) to the ViewContainerRef
Now I want to "submit" the form. To do this, I thought I could simply use the ViewContainerRef and get its "siblings" (which are dynamically created based on the before mentioned JSON) and loop through them and create a JSON object to post this.. But I don't understand how the Angular2 DOM works..
Here is the (partial) code I've got so far:
addComponent function
//... class DynamicComponent, has id and value properties
public addComponent(container: ViewContainerRef, template: string){
#Component({template: template})
class TemplateComponent{
public value = "test";
}
#NgModule({declarations: [TemplateComponent]})
class TemplateModule {}
const mod = this.compiler.compileModuleAndAllComponentsSync(TemplateModule);
const factory = mod.componentFactories.filter((comp)=>
comp.componentType === TemplateComponent
);
const component = container.createComponent(factory[0]);
return TemplateComponent;
}
Create Components
#ViewChild("container",{ read: ViewContainerRef }) container : ViewContainerRef;
..
//foreach JSON element
let _component = <any>
this._dynamicComponent.addComponent(
this.container, elementObject.render()
);
//the render() returns things like : `<Label text="foo"></Label>`
..
_component.prototype.onTap = () => {
this.submit();
}
Submit Function
submit(){
let json=[];
for(let i=0;i<this.container.length;i++){
let component = this.container.get(i);
//Ideally I want to do this:
/*
json.push({
id:component.id,
val:component.value
});
*/
}
}
Basically, I get "component" in the submit function as a ViewRef object, but how do I continue from there?
Also, I'm fairly new to Angular2, so is this the right way? I've seen the form builder but I didn't get it to work properly with dynamically created elements..
Related
I am trying to implement myown cell renderer following this example https://www.ag-grid.com/javascript-data-grid/cell-rendering/ , but in typescript .
When I declare a class which implements ICellRenderer interface and try to implement the inteface methods I get a compile time error
"Property 'eGui' does not exist on type 'MessageCellRenderer'."
This is part of my ts file where I define the renderer as well as the view which contains the grid.
NOTE: The grid displays fine without the cell renderer but I need to style the cells based on displayed values , hence trying to use the cell render.
import {ErrorFilterView} from "./error-dialog/error-filter-view";
import {ICellRenderer} from 'ag-grid-community/main';
var agGrid = require('../../../../node_modules/ag-grid-community');
class MessageCellRenderer implements ICellRenderer{
init(params) {
this.eGui = document.createElement('span');
if (params.value !== '' || params.value !== undefined) {
this.eGui.innerHTML = `'<span class="error_read_#: params.value# error-item_#: Id#">#:Message#</span>'`;
}
}
// gets called once when grid ready to insert the element
public getGui() {
return this.eGui;
};
// gets called whenever the user gets the cell to refresh
public refresh(params) {
// set value into cell again
this.eValue.innerHTML = params.value;
};
// gets called when the cell is removed from the grid
public destroy() {
// do cleanup, remove event listener from button
};
}
export var NotificationsDialogView = (viewModel : ErrorDialogViewModel) => {
if (!viewModel.getIsInitialized()) return;
var messageGrid: any;
var config = (element : any, isInitialized : boolean, context : any) => {
mdlInit(element, isInitialized, context);
Figured this out - just needed to add the required variable definitions as the example was using javascript as opposed to typescript which is a strongly typed (kind of) languauge
I am still learning and I got stuck so I need to ask a question. My understanding of Input Output decorators is that I need to add selector to html of parent to be able to use them, but for my case I don't think it's the way to go, but someone can prove me wrong.
CASE: For readability purposes I have split components. I have one component, data-fetch-transform that gets the data form local JSON file and does some adjustments to it, and another one, that wants to take that data for further use.
PROBLEM: I am unsure how to read the data from one component in the other. On the example below, how can I get countryNumber and centerNumber result in my other component. I intend to have data-fetch-transform.component.ts just manipulate the data and used in other components
Target component
project/src/app/data-use/data-use.component.ts
Data Source component
project/src/app/data-fetch-transform/data-fetch-transform.component.ts
import { Component, OnInit } from '#angular/core';
import * as data from '../../../../../data/Data.json';
#Component({
selector: 'app-datafetch-transform',
templateUrl: './datafetch-transform.component.html',
styleUrls: ['./datafetch-transform.component.css'],
})
export class DatafetchComponent implements OnInit {
public dataList: any = (data as any).default;
dataPointCount = this.data.length!!;
uniqueValues = (dt: [], sv: string) => {
var valueList: [] = [];
for (let p = 0; p < this.dataPointCount; p++) {
valueList.push(dt[p][sv]);
}
var uniqueValues = new Set(valueList);
return uniqueValues.size;
};
countryNumber=this.uniqueValues(this.dataList, 'Country')
centerNumber=this.uniqueValues(this.dataList, 'Center Name')
constructor() {}
ngOnInit(): void {}
}
You don't need another component for data manipulation (data-fetch-transform), you need a service (data-fetch-transform-service) where you should do the logic.
HERE IS WHAT YOU SHOULD HAVE IN THE SERVICE
private _dataList = new behaviorSubject([]);
public dataList$ = _dataList.asObservable();
for (let p = 0; p < this.dataPointCount; p++) {
// ... do your thing
_valueList.next(result);
}
and in the component you just subscribe to the service:
declarations:
private _subscription = new Subscription()
in constructor:
private dataService:DataFetchTransformService
and in ngOnInit:
this_subscription.add(this.dataService.dataList$.subscribe((response:any)=>{
this.data = response;
}))
in ngOnDestroy():
ngOnDestroy(){
this._subscription.unsubscribe();
}
I strongly suggest to stop using any since it can bring a lot of bugs up.
Also, as a good pattern, I always suggest use behaviorSubject only in the service as a private variable and user a public observable for data.
WHY IS BETTER TO USE A SERVICE
You can subscribe from 100 components and writing only 4 lines of code you bring the data anywhere.
DON'T FORGET TO UNSUBSRIBE IN ngOnDestroy
If you don't unsubscribe, you'll get unexpected behavior.
I have written custom HTML elements whose constructors and definitions themselves are in classes. I have done this in order to create a view. However, I need to create an app with a MVC, model-view-controller design pattern. I've seen several posts but all of them involve standard HTML elements. How can I do this with my custom HTML elements (tho not web-components; they don't use a shadow DOM)
ps: i'm not using Jquery but rather standard selectors.
You can use the MVC pattern for Custom Elements just like with standard Javascript.
Define:
one object (or a class) for the Controller
one for the Model
one for the View
There are many ways to define and to interconnect the Model, View and Controller.
Custom Element specific
Adapted to the Custom Element, you can either:
define the Custom Element class (extending HTMLElement) as the Controller
define the Custom Element class as the View
define the the Model, View and Controller inside the Custom Element class
define the 3 entities outside the Custom Element class
etc.
Example
In the minimalist example implemented below:
the Custom Element class defines the Controller
the Model is declared in the Controller as a function
then the View is declared in the Controller as a function, too
This way the View can access the Model (for data reading) and the Controller directly.
//CONTROLLER
class PersonController extends HTMLElement {
connectedCallback() {
let controller = this
let elem = this
//MODEL
function PersonModel () {
//Model API
this.load = async id => {
let res = await fetch( `https://reqres.in/api/users/${id}` )
let json = await res.json()
Object.assign( this, json.data )
controller.updateView()
}
}
let model = new PersonModel
//VIEW
function PersonView () {
elem.innerHTML = `
Id : <input id=index type=number>
<div id=card>Loading data
</div>`
let card = elem.querySelector( 'div#card' )
let index = elem.querySelector( 'input#index' )
index.onchange = () => controller.init( index.value)
//View API
this.update = () => {
card.innerHTML = `
<div>Name : ${model.first_name} ${model.last_name}</div>
<div><img src=${model.avatar}></div>`
index.value = model.id
}
}
let view = new PersonView
//Controller API
this.init = id => model.load( id )
this.updateView = () => view.update()
//Init with some data
this.init( 4 )
}
}
customElements.define( 'person-card', PersonController )
person-card { display: inline-block ; background-color: lightblue }
person-card input { width: 50 }
<person-card></person-card>
This example creates a <person-card> element with the MVC pattern that will fetch some data from a REST web service (at reqres.in) and show a person's name, id, and avatar.
I need to add class name to some Vue components using their ref names. The ref names are defined in a config file. I would like to do it dynamically, to avoid adding class manually on each Vue component.
I have tried to find each component using $refs and if found, add the class name to the element's class list. The class is added, but it is removed as soon as user interaction begins (e.g. the component is clicked, receives new value etc.)
Here is some sample code I've tried:
beforeCreate() {
let requiredFields = config.requiredFields
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
You can use this:
this.$refs[field].$el.classList.value = this.$refs[field].$el.classList.value + 'my-class'
the only thing that you need to make sure of is that your config.requiredFields must include the ref name as a string and nothing more or less ... you can achieve that with :
//for each ref you have
for (let ref in this.$refs) {
config.requiredFields.push(ref)
}
// so config.requiredFields will look like this : ['one','two]
here is an example of a working sample :
Vue.config.devtools = false;
Vue.config.productionTip = false;
Vue.component('one', {
template: '<p>component number one</p>'
})
Vue.component('two', {
template: '<p>component number two</p>'
})
new Vue({
el: "#app",
beforeCreate() {
let requiredFields = ['one','two'] // config.requiredFields should be like this
this.$nextTick(() => {
requiredFields.forEach(field => {
if(this.$refs[field]) {
this.$refs[field].$el.classList.add('my-class')
}
})
})
}
})
.my-class {
color : red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<one ref="one" ></one>
<two ref="two" ></two>
</div>
I know this question was posted ages ago, but I was playing around with something similar and came across a much easier way to add a class to $refs.
When we reference this.$refs['some-ref'].$el.classList it becomes a DOMTokenList which has a bunch of methods and properties you can access.
In this instance, to add a class it is as simple as
this.$refs['some-ref'].$el.classList.add('some-class')
You've to make sure classList.value is an array. By default its a string.
methods: {
onClick(ref) {
const activeClass = 'active-submenu'
if (!this.$refs[ref].classList.length) {
this.$refs[ref].classList.value = [activeClass]
} else {
this.$refs[ref].classList.value = ''
}
},
},
this post helped me tremendously. I needed to target an element within a v-for loop and I ended up writing a little method for it (i'm using Quasar/Vue).
hopefully this will save someone else some time.
addStyleToRef: function(referEl, indexp, classToAdd) {
//will add a class to a $ref element (even within a v-for loop)
//supply $ref name (referEl - txt), index within list (indexp - int) & css class name (classToAdd txt)
if ( this.$refs[referEl][indexp].$el.classList.value.includes(classToAdd) ){
console.log('class already added')
} else {
this.$refs[referEl][indexp].$el.classList.value = this.$refs[referEl][indexp].$el.classList.value + ' ' + classToAdd
}
}
let tag = this.$refs[ref-key][0];
$(tag).addClass('d-none');
Simply get the tag with ref let tag = this.$refs[ref-key][0]; then put this tag into jquery object $(tag).addClass('d-none'); class will be added to required tag.
I'm new to JavaScript and probably trying to emulate Ruby with this. I use StimulusJS, but I think the question is applicable to JS in general.
My goal is to run a method (on button click) which would fetch and display all subcategories of the category from the button. The method/function would first fetch the data and then manipulate the DOM. I wanted to split these into two methods, but when I call the other method (displaySubcategories) from within the first one (getSubcategories) the value of event changes. Same thing with this in the fetch block - I need to assign it first to self to be be later able to related to object itself.
Is there a better way to do this? Like variables accessible to all methods (instance variables in Ruby)? Or I shouldn't be splitting these into two functions at all (not JS way)?
import {Controller} from "stimulus"
export default class extends Controller {
static targets = ["categoryId", "categoryLabel", "subcategoryList", "subcategoryHeader"]
getSubcategories() {
let category_id = event.target.firstElementChild.value
let url = "/api/categories/" + category_id + "/subcategories?levels=1"
let self = this
let e = event
fetch(url)
.then(response => response.json())
.then(json_response => {
self.displaySubcategories(json_response, e)
})
}
displaySubcategories(json_response, event) {
subcategories.forEach(function (subcategory) {
let category_label_copy = this.cloneLabel(current_label, subcategory)
current_label.classList.add("chosen")
subcategory_list.appendChild(category_label_copy)
}, this)
}
}
expenses#getSubcategories">