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

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;
}

Related

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.

Simple way to nest template-driven forms Angular

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

ngInit VS. Firing Controller Function - What are the Downsides?

I have a couple questions regarding the use of ng-init - I've see many people online recommend substituting ng-init for running the desired function as soon as the controller is ready.
What if I have two vies that use the same controller, but I only want one of the views to trigger a specific controller function, like so
index.html
<div ng-controller="myController">
{{ someStuff }}
</div>
<div ng-controller="myController" ng-init="run()">
{{ someOtherStuff }}
</div>
app.js
.controller('myController', function($scope){
$scope.someStuff = 'ABC';
$scope.run = function(){
$scope.someOtherStuff = 'XYZ';
}
}
In this scenario, what is the real downside of using ng-init to call run()? Would there be any issues if run() was an asynchronous function?
In my opinion this seems to the the job just fine, but I may have glossed over why this is a bad idea. Any input is appreciated!
I personally avoid using ng-init unless there are NO other ways that I can solve my problem. That's really what ng-init is supposed to be used for, but you'll find a plethora of bad usages of ng-init everywhere you look.
As per the ng-init documentation:
This directive can be abused to add unnecessary amounts of logic into your templates. There are only a few appropriate uses of ngInit, such as for aliasing special properties of ngRepeat, as seen in the demo below; and for injecting data via server side scripting. Besides these few cases, you should use controllers rather than ngInit to initialize values on a scope.
A better idea would be to simply use another controller and map your templates 1:1 with the controllers.

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!

Where should I place common code when writing angular directives?

I'm in a situation where I would like to have many directives with different
tag names, but which are nearly identical in their behaviour. My first idea
was to just capture the directive as a function like this:
function standardDirective(template,extract) {
return function() {
return {
restrict: 'E',
... }
}
}
and then use this function to 'stamp out' the required directives.
angular.module('MCQ', [])
.directive('mcq'
, standardDirective("MCQTemplate.html"
, function(scope){return scope.userSelection;}
)
);
After writing this, I'm pretty sure that there is some common, and possibly better
idiom, for doing this. For example, in my 'solution' I probably can't make angular load the standardDirective when it is needed.
(I know that I could make a single directive and use attributes to differentiate behaviour, but for now, let's assume that I really need different directives..)
You could use the require option on the directive.
This essentially ensures that your directive "inherits" another directive, like a base class.
Your 'base' directive can contain all of your common logic and then then other directive that is requiring it has it's own spin on whatever it needs to do.
you can read more about it in the Angular Docs - see the Creating Directives that Communicate section

Categories

Resources