Simple way to nest template-driven forms Angular - javascript

I've been going through similar questions in both StackOverflow and GitHub, but all approaches to a solution are attached to a particular case and that's really not my goal as I have several applications with different case-scenarios and ideally I would like to have transferrable code (isn't that ultimate goal of Software Development anyway?)
I have a big form with deep nesting (4 levels)
Now, I would much rather avoid learning all about ReactiveForms at this point, but I just can't find a simple way to nest template-driven either
Let's say I have a form like:
<form class="container-fluid" name="editForm" #editForm="ngForm">
Now I have a number of fields, with two-way bindings
And now I need to introduce my first level of nesting, I declare the component like this:
#Component({
selector: 'jhi-survey-data',
templateUrl: 'surveyData.template.html',
})
Export the class and bring inputs to the controller code:
export class SurveyDataFormComponent implements OnInit {
#Input() formDTO: FormDTO;
#Input('surveyDataDTO') surveyDataDTO: any;
#Input('i') i: number;
And I attach it to the DOM like:
<div *ngFor="let surveyDataDTO of formDTO.surveyDataDTOs; let i=index;">
<jhi-survey-data
[formDTO]="formDTO"
[surveyDataDTO]="surveyDataDTO"
[i]="i">
</jhi-survey-data>
</br></br>
</div>
The issue here is that when I try to save changes, they will save fine for any changes on the top component, but not at all on the second.
I've tried by a shared service, accessing the component from there when saving and trying to pick up data, but it comes back as undefined, I guess the "connection" (lacking a better way to describe it) to the objects in the component is closed to any access outside the main component
I've tried grouping the <jhi-survey_data> in a ngModelGroup and to the exact same result
I guess the question here is if there is actually a simple way to nest template-driven forms or is this going to be a source of pain until Angular team decides if it's enough of us not willing to learn ReactiveForms?
I really don't want to use workarounds or boilerplate code

Related

Angular Reactive Forms, is it a bad practice to use "myForm.get('myFormControl'')" in the template?

I know that using function calls in Angular templates is a bad practice. In short it is because Change Detection will make the function run many times, which will lead to bad performance. (This article goes more into depth on the topic)
I have been following this rule closely except from one exception, and that is when I need the value from my Reactive Form in my template. Usually I use it like this:
My way of doing it
Template: <div *ngIf="myFormGroup.get('name').invalid">Filling out name is required</div>
Here I am not following the rule because I am calling the function myFormGroup.get('name') inside my template. To solve this I decided to go to the Angular documentation to see how they do this, and I found a similar example here: https://angular.io/guide/form-validation#built-in-validator-functions.
What the Angular documentation does is that they put the form control in a getter, and then they use the getter in the template, like this:
Angular documentation way of doing it
Component class: get name() { return this.myFormGroup.get('name'); }
Template: <div *ngIf="name.invalid">Filling out name is required</div>
My questions are
Is it ok to use "myFormGroup.get('name')" in a template, even though it breaks the rule of not having function calls in the template?
Is the Angular Documentation way of doing this any different from my way of doing it when it comes to performance? (My understanding is that using a getter this way does noe solve the Change Detection Performance issue)
You can do like this:
In Template:
<input type="text" id="name" formControlName="name" />
<div *ngIf="isControlInvalid('name')">
Something
</div>
In Component:
isControlInvalid(controlName: string): boolean {
const control = this.myForm.controls[controlName];
const result = control.invalid && control.touched;
return result;
}

I have some questions about Sapper/Svelte

I just started using Sapper (https://sapper.svelte.technology) for the first time. I really like it so far. One of the things I need it to do is show a list of the components available in my application and show information about them. Ideally have a way to change the way the component looks based on dynamic bindings on the page.
I have a few questions about using the framework.
First, I'll provide a snippet of my code, and then a screenshot:
[slug].html
-----------
<:Head>
<title>{{info.title}}</title>
</:Head>
<Layout page="{{slug}}">
<h1>{{info.title}}</h1>
<div class="content">
<TopBar :organization_name />
<br>
<h3>Attributes</h3>
{{#each Object.keys(info.attributes) as attribute}}
<p>{{info.attributes[attribute].description}} <input type="text" on:keyup="updateComponent(this.value)" value="Org Name" /></p>
{{/each}}
</div>
</Layout>
<script>
import Layout from '../_components/components/Layout.html';
import TopBar from '../../_components/header/TopBar.html';
let COMPONENTS = require('../_config/components.json');
export default {
components: {
Layout, TopBar
},
methods: {
updateComponent(value) {
this.set({organization_name: value});
}
},
data() {
return {
organization_name: 'Org Name'
}
},
preload({ params, query }) {
params['info'] = COMPONENTS.components[params.slug];
return params;
}
};
</script>
Now my questions:
I notice I can't #each through my object. I have to loop through its keys. Would be nice if I could do something like this:
{{#each info.attributes as attribute }}
{{attribute.description}}
{{/each}}
Before Sapper, I would use Angular-translate module that could do translations on strings based on a given JSON file. Does anyone know if a Sapper/Svelte equivalent exists, or is that something I might need to come up with on my own?
I'm not used to doing imports. I'm more use to dependency injection in Angular which looks a bit cleaner (no paths). Is there some way I can create a COMPONENTS constant that could be used throughout my files, or will I need to import a JSON file in every occurence that I need access to its data?
As a follow-up to #3, I wonder if there is a way to better include files instead of having to rely on using ../.. to navigate through my folder structure? If I were to change the path of one of my files, my Terminal will complain and give errors which is nice, but still, I wonder if there is a better way to import my files.
I know there has got to be a better way to implement what I implemented in my example. Basically, you see an input box beside an attribute, and if I make changes there, I am calling an updateComponent function which then does a this.set() in the current scope to override the binding. This works, but I was wondering if there was some way to avoid the function. I figured it's possible that you can bind the value of the input and have it automatically update my <TopBar> component binding... maybe?
The preload method gives me access to params. What I want to know if there is some way for me to get access to params.slug without the preload function.
What would be really cool is to have some expert rewrite what I've done in the best possible way, possibly addressing some of my questions.
Svelte will only iterate over array-like objects, because it's not possible to guarantee consistent behaviour with objects — it throws up various edge cases that are best solved at an app level. You can do this sort of thing, just using standard JavaScript idioms:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} ...</p>
{{/each}}
<!-- or, if you need the key as well -->
{{#each Object.entries(info.attributes) as [key, value]}}
<p>{{attr.description}} ...</p>
{{/each}}
Not aware of a direct angular-translate equivalent, but a straightforward i18n solution is to fetch some JSON in preload:
preload({ params, query }) {
return fetch(`/i18n/${locale}.json`)
.then(r => r.json())
.then(dict => {
return { dict };
});
}
Then, you can reference things like {{dict["hello"]}} in your template. A more sophisticated solution would only load the strings necessary for the current page, and would cache everything etc, but the basic idea is the same.
I guess you could do this:
// app/client.js (assuming Sapper >= 0.7)
import COMPONENTS from './config/components.json';
window.COMPONENTS = COMPONENTS;
// app/server.js
import COMPONENTS from './config/components.json';
global.COMPONENTS = COMPONENTS;
Importing isn't that bad though! It's good for a module's dependencies to be explicit.
You can use the resolve.modules field in your webpack configs: https://webpack.js.org/configuration/resolve/#resolve-modules
This would be a good place to use two-way binding:
{{#each Object.values(info.attributes) as attr}}
<p>{{attr.description}} <input bind:value=organization_name /></p>
{{/each}}
Yep, the params object is always available in your pages (not nested components, unless you pass the prop down, but all your top-level components like routes/whatever/[slug].html) — so you can reference it in templates as {{params.slug}}, or inside lifecycle hooks and methods as this.get('params').slug, whether or not a given component uses preload.

How to have all the static strings in one place

I am creating a vue webapp, I have few pages with Dynamic content and also few pages which has mostly static content. I want to move all these static strings to one place.
One option can be to use vue-i18n or vue-multilanguage, these gives support to have content files like this, but I really have no use case of support of multiple languages, so it also seems a bit over kill to me.
Another option can be to have a vuex store for all the strings, vuex I am already using for state management.
What can be good approach to do this.
I am not aware of a standard way of doing this, also this would be applicable to all the web frameworks. That said it is an interesting and valid problem.
If I had to do something about it:
I would want these strings to be available everywhere.
I would prefer not having to import these strings in all the components and each time I needed to use them.
I would want the storage space to be descriptive so that I don't have to go back and forth to check what I want to import. [The toughest part in my opinion]
To achieve 1, we can use:
Vuex
A services/some.js file which exports an object.
Plugins
I would go with plugins because:
I can get the strings by merely using this in a component, Vue.use(plugin) prevents the same plugin getting used twice, and at the same time achieve all the points (3rd will still be a tough nut to crack). Only disadvantage that I know of it might clutter the vue-instance.
So plugin can be designed like:
// stringsHelperPlugin.js
const STRING_CONST = {
[component_1_Name]: {
key1: val1,
key2: val2,
....
},
[component_2_Name]: {
key1: val1,
key2: val2,
....
},
...
}
StringConst.install = function (Vue, options) {
Vue.prototype.$getStringFor = (componentName, key) => {
return STRING_CONST['componentName'][key]
}
}
export default StringConst
in main.js this can be used like:
import StringConst from 'path/to/plugin'
Vue.use(StringConst)
and you could use this in a component template like so:
<div>
{{ $getStringFor(<component_1_name>, 'key1') }}
</div>
You can use something like this.$getStringFor(<componentName>, key) in a method. Pretty much everything that vuejs to has to offer.
Why I call the 3rd point hardest is: Maintainance if you ever change component names, you might also have to change it in the object returned by the plugin. This problem again, can be handled in many ways.
You can make an npm module with JSON files containing your strings
If you don't use vuex in your project, put your content in some javascript files which will be basically objects with all your static content and import them where you need just like Belmin menionted I am using Vue js and python flask as my backend. I want to have some local variable set. How can it be done?
A similar approach can be used for urls, configurations, errors etc.
If you use vuex, centralize everything there and make getters which you can use in each of your components.

How to use templateRef?

I am trying to find a way to dynamically construct a template in Angular2. I was thinking templateRef might provide a way to do this. But I could be wrong.
I found an example of templateRef being used here.
I was looking at templateRef in this example. I noticed the syntax is [ng-for-template] I also tried [ngForTemplate] cause I know this has changed recently.
So at the moment I have this:
import {Component, TemplateRef} from 'angular2/core';
#Component({
selector : 'body',
template : `
<template [ngForTemplate]="container">
<div class="container"></div>
</template>
`
})
export class App
{
#ContentChild(TemplateRef) container;
constructor() {}
ngAfterContentInit()
{
console.log(this);
}
}
This example throws an error:
Can't bind to 'ngForTemplate' since it isn't a known native property
So firstly I am wondering. What is the right way to do this? The docs don't provide any examples.
Secondly, is there a good way I can add new template logic to my template or dynamically construct a template? The structure of the application can be a very large amount of different structural combinations. So if possible I would like to see if there is a way I can do this without having a huge template with a bunch of different ngIf and ngSwitch statements..
My question is really the first part about templateRef. But any help or suggestions on the second part is appreciated.
Creating your own template directive it's not difficult, you have to understand two main things
TemplateRef contains what's inside your <template> tag
ViewContainerRef as commented by Gunter, holds the template's view and will let you to embed what's inside the template into the view itself.
I will use an example I have when I tried to solve this issue, my approach is not the best for that, but it will work for explaining how it works.
I want to clarify too that you can use any attribute for your templates, even if they're already used by builtin directives (obviously this is not a good idea, but you can do it).
Consider my approach for ngIfIn (my poor approach)
<template [ngIfValue]="'make'" [ngIfIn]="obj">
This will print
</template>
<template [ngIfValue]="'notExistingValue'" [ngIfIn]="obj">
This won't print
</template>
We have here two templates using two inputs each ngIfIn and ngIfValue, so I need my directive to grab the template by these two inputs and get their values too, so it would look like this
#Directive({
selector : '[ngIfIn][ngIfValue]',
inputs : ['ngIfIn', 'ngIfValue']
})
First I need to inject the two classes I mentioned above
constructor(private _vr: ViewContainerRef, private _tr: TemplateRef) {}
I also need to cache the values I'm passing through the inputs
_value: any;
_obj: any;
// Value passed through <template [ngIfValue]="'...'">
set ngIfValue(value: any) {
this._value = value;
}
// Value passed through <template [ngIfIn]="...">
set ngIfIn(obj: any) {
this._obj = obj;
}
In my case I depend on these two values, I could have my logic in ngOnInit but that would run once and wouldn't listen for changes in any of the inputs, so I put the logic in ngOnChanges. Remember that ngOnChanges is called right after the data-bound properties have been checked and before view and content children are checked if at least one of them has changed (copy and paste from the docs).
Now I basically copy & paste NgIf logic (not so complex, but similar)
// ngOnChanges so this gets re-evaluated when one of the inputs change its value
ngOnChanges(changes) {
if(this._value in this._obj) {
// If the condition is true, we embed our template content (TemplateRef) into the view
this._vr.createEmbeddedView(this._tr);
} else {
// If the condition is false we remove the content of the view
this._vr.clear();
}
}
As you see it's not that complicated : Grab a TemplateRef, grab a ViewContainerRef, do some logic and embed the TemplateRef in the view using ViewContainerRef.
Hopefully I made myself clear and I made how to use them clear enough also. Here's a plnkr with the example I explained.
ngForTemplate is only supported with ngFor
<template [ngFor] [ngForOf]="..." [ngForTemplate]="container"
or
<div *ngFor="..." [ngForTemplate]="container"
not on a plain template. It is an #Input() on the NgFor directive
Another way to use TemplateRef
If you have a reference to ViewContainerRef you can use it to "stamp" the template
constructor(private _viewContainer: ViewContainerRef) { }
ngOnInit() {
this.childView = this._viewContainer.createEmbeddedView(this.templ);
this.childView.setLocal('data', this.data);
}

Blaze Meteor dynamically instanciate template and datacontext

I'm dynamically instanciating template on event / or array change (with observe-like functionality).
To achieve that, I use
//whatever event you want, eg:
$(".foo").on("click", function(){
Blaze.renderWithData(Template.widgetCard, d, $(".cards").get(0));
}
That is working, but obviously, instances aren't bound to any parent's template.
Because I just rendered this template on the div.cards I'm unable to use the Template.parentData(1) to get the parent datacontext, even so this div.cards is include on a template.
The quick fix would be to set the wanted reference (which in my case is an object) variable parent's datacontext on global scope, or even use Session, or directly pass this context through the renderWithData's data.
Do you know any other way,even better the proper one (I mean Meteor fancy one), to achieve that?
Is it a good Blaze.renderWithData use case?
Tell me if i'm unclear or more code is needed.
EDIT:
Complementary context info:
I've a chart (d3) where it's possible to select some parts of it.
It has an array property to stock this selected data part.
Chart = function Chart(clickCb, hoverCb, leaveCb, addSelectionCb, removeSelectionCb){
var chart = this;
chart.selectedParts = [];
//... code
}
From outside of this Chart class (so on the Meteor client side), the chart.selectedParts is modified (add/delete).
The dream would be to "bind" this array chart.selectedParts like:
Template.templateContainingAllThoseCards.helpers({
selectedDataChart: function(){
return Template.instance.chart.selectedParts;
},
//...
});
and on the template being able to do something like that:
<div class="row">
<div class="large-12 columns">
<div class="cards">
{{#each selectedDataChart}}
{{> cardWidget}}
{{/each}}
</div>
</div>
</div>
Like that, if the chart.selectedParts was reactive, Blaze could automatically create or remove cardWidget template instance due to the binding.
I've tried to use manuel:reactivearray package on it (and it's kind of anoying cause I'm doing complex manipulation on this array with Underscore, which obviously don't work with none-native Array type such reactiveArray).
Not working, but I dunno if it should have worked.
What do you think?
At this time, I'm doing things a bit dirty I suppose; I juste instanciate/destroying Blaze View on element added/removed chart.selectedParts as: Blaze.renderWithData(Template.widgetCard, {data: d, chart: this}, $(".cards").get(0));
So here how I manage to do that.
Actually I don't think using Blaze.renderWithData() is a good solution.
Best way I've found is to pass your data on "Reactive mode", to be able to use all Template functionalities, and keep using Spacebars to instanciate templates. (Like parent DataContext link).
Easiest way to have reactive datasource is to always match your data with your Mongo, so I don't have to declare a custom Reactive Data source (which could be tricky with complex from a complex js data structure).
If someone have the same problem, I'm pretty sure it's because you don't follow the "good" way to do (which was my case).
One con with always updating your DB as reactive Data source should be a case where you're doing a lot of UI state change, and after all, saving the change. On this case, it's pretty useless to always pass by the DB, but it's from far the quickest solution.
Ask me if you have any similar issue understanding philosophy/way to do, I'm starting to understand what i'm doing!

Categories

Resources