Is a bad practice to be creating divs in your HTML just to be able to have multiple controllers?
Since you cannot have them in one also the approach of having multiple directives with separate controllers seems like a hack, but correct me if I am wrong.
So I have 2 controllers - one is called ConvertController and the other one is called YouTubeLinkUIController.
The responsibility of the first one is to hit my Web API controller and convert the given YouTube link into an audio.
The responsibility of the second one is to control some of the UI functionality.
In order to follow the single responsibility principle, I have separated them into 2 different controllers and therein lies the problem.
My last commit, specifically Index.cshtml shows the full code, but for the sake of not making this messy I will abreviate it for this post.
https://github.com/AvetisG/TubeToTune/commit/e68ba42f20498c9a4e032328aed6d68c27b9b2cb
<div class="container" ng-app="TubeToTuneApp" ng-controller="YouTubeLinkUIController">
... more code
#*Scoping the Convert Controller*#
<div ng-controller="ConvertController">
... more code
</div>
</div>
Looking at your github commit message it looks like you split the controllers because you didn't want to have too much code in your ui controller.
This is a perfect example of when an angular service is in order. You can think of a service like a controller without all the overhead and that can be easily called from another controller
Angular Service Documentation
What you should do is define a service that calls your api and then
angular.module('youTube', [])
.controller('YouTubeLinkController', ['$scope', 'convert',
function($scope, convert) {
$scope.convertLink = function() {
$scope.convertMessage = convert.convertYoutube()
};
}
])
.factory('convert', [
function() {
var convertService = {};
convertService.convertYoutube = function(data) {
//Make api call
return "Conversion Done";
}
return convertService;
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="youTube">
<div ng-controller="YouTubeLinkController">
{{convertMessage}}
<button ng-click="convertLink()">Convert</button>
</div>
</body>
Naturally you should define the service in it's own page. That way controllers are only used for interacting with UI. It also solves your problem
It's absolutely fine to use divs\spans and nested structures and use ng-controller with it.
When you add ng-controller to a html element in a way you are creating a component with a model-view and controller.
A complex component can have nested sub components that perform very specific functionality and in a way you are demarcating such parts with ng-controller . To take it a step further if you convert these component into directives, with their own controller + template(view) and that accept model from a data source, then you have a reusable component that you can use throughout the app.
To me, it looks more like your ConvertController should be written as a service, rather than as a controller. This would still follow single responsibility, but would probably align closer to your intended functionality: UI functionality and view data in the controller, business logic in the service.
That said, having two controllers on a single view might not be bad practice, this leads to confusing mark-up in cases like the following:
<button type="submit" class="btn btn-xlarge btn-warning btn-block" ng-if="AreThereYouTubeLinks() == true" ng-click="ConvertToTunes(YouTubeLinks)">
You may know personally which controllers AreThereYouTubeLinks(), ConvertToTunes(), and YouTubeLinks belong to, but this will be confusing in the long-term (and can lead to issues with scope-bound variables like YouTubeLinks.
Luckily, there's a syntax for helping clear this up- Controller As - and Todd Motto wrote an excellent article explaining how to use it and what problems it helps solve. In brief, it would turn something like this:
<div ng-controller="MainCtrl">
{{ title }}
<div ng-controller="AnotherCtrl">
{{ title }}
<div ng-controller="YetAnotherCtrl">
{{ title }}
</div>
</div>
</div>
in to this:
<div ng-controller="MainCtrl as main">
{{ main.title }}
<div ng-controller="AnotherCtrl as another">
{{ another.title }}
<div ng-controller="YetAnotherCtrl as yet">
{{ yet.title }}
</div>
</div>
</div>
In your case, you would end up with this safer, more understandable mark-up:
<div class="container" ng-app="TubeToTuneApp" ng-controller="YouTubeLinkUIController as linkCtrl">
... more code
#*Scoping the Convert Controller*#
<div ng-controller="ConvertController as converter">
<button type="submit" class="btn btn-xlarge btn-warning btn-block" ng-if="linkCtrl.AreThereYouTubeLinks() == true" ng-click="converter.ConvertToTunes(linkCtrl.YouTubeLinks)">
</div>
</div>
If you're going to stick with two controllers, you probably want to consider investing the time to getting comfortable with Controller As.
Related
I'd like implement a solution where a user can make a request to the backend and since the request takes some time to get the answer from the backed.
I want to render the same page with a loading animation without javascript. For this purpose, I set a variable "show_loading" on "true" to display the loading animation via the hmtl-file. The code is as follows:
views.py
def UploadView(request):
show_loading = True
context["show_loading"] = show_loading
render(request,'UPLOAD.html',context)
data = {}
#something else happening, just logical operations
return render(request,'UPLOAD.html',context)
UPLOAD.html:
{% if show_loading %}
<div class="loader">
<div class="inner one"></div>
<div class="inner two"></div>
<div class="inner three"></div>
</div>
{% endif %}
The problem is that the render function is not working by the first render line (without return) but with the second render line (with return). So the question is, does render always need return or what I am doing wrong here, that the render does not work without return?
You cannot use the backend to update the page with a classic Django view, since to communicate with the frontend you need to return a response (yes, render must return). The easiest way is to use Javascript: set a simple timer that updates the page if it does not receive a response after a certain time. If you really want to use the backend you need to create a channel between the backend and the frontend, take a look at this.
In some instances, I need to just repeat some html code within my Template to DRY it up, but making a new component and passing a ton of props and dynamic data to it seems like overkill. Is there a way to define a repeatable block of template code that can just be reused?
A good example of this is my vuelidate validation error messages that are repeated. I don't want to create an entire vue component for them because then I need to pass in the validation, validation prop and a few other things so that seems like creating more complexity just to DRY up a little bit of the template.
I have this block of code on three different scenarious in the same template, is there a way I can just define them as a block to reuse. Literally nothing changes so it's very much against DRY principles.
<span
v-if="!$v.initialReplyText.required"
class="error">Your reply cannot be empty.</span>
<span
v-if="!$v.initialReplyText.maxLength"
class="error">Your reply cannot be over 2,000 characters.</span>
you can do dynamic binding using v-bind, that way you don't need to bind all properties individually.
<!-- pass down parent props in common with a child component -->
<child-component v-bind="$props"></child-component>
src: https://v2.vuejs.org/v2/api/#v-bind
You can also use slots, or scoped slots, which are commonly used for things like wrapping error messages in more complex markup.
If all elements are consecutively arranged as in your example, you can use v-for as below:
<span v-for="(criteria, msg) in {'Your reply cannot be empty.': !$v.initialReplyText.required, 'Your reply cannot be over 2,000 characters.': !$v.initialReplyText.maxLength }"
v-if="criteria" class="error">
{{msg}}
</span>
I create html components with Thymeleaf. Components are declared in separate file:
Declaration of basic button in buttons.html
<div th:fragment="btn-basic" class="btn btn-basic" th:text="${text}" th:classappend="${class}">
Button
</div>
The idea is to provide some type of tool-set for components. Code for using this component will be:
<div th:replace="~{buttons :: btn-basic (text='Button Text', class='button-class')}"></div>
It's working well, but I think about case when button need to have attributes like: onclick="..." or data-customAttr="..." or any other attribute. And here goes the problem:
How to pass attributes to button?
One way is to pass it as parameter of fragment, but it's too ugly.
Is there any way to get attributes of placeholder in fragment? (see example below)
This how I want to call fragment:
<div th:replace="~{buttons :: btn-basic (text='Button Text', class='button-class')}" onclick="..." data-customAttr="..."></div>
and in btn-basic fragment want to get these attributes and attach to it. Something like this:
<div th:fragment="btn-basic" class="btn btn-basic" th:text="${text}" th:classappend="${class}" onclick="..." data-customAttr="...">
Button
</div>
Any ideas?
I had a similar idea, but the question is, if the customizing of a component is as complex as the result, what is the benefit?
Btw. with the Thymeleaf Layout Dialect you can do something like this: https://ultraq.github.io/thymeleaf-layout-dialect/Examples.html#reusable-templates, I favor that, instead of the everything-as-parameter approach.
I first initialize my app with ng-app="myApp" in the body tag and this works fine for all angularized-html that is loaded on first page load.
Later on I have some code that loads angularized-html in to the DOM.
In angular 1.08 I could just run angular.bootstrap($newLoadHTML, ["myApp"]) after the load and it would work; where $newLoadHTML is the newly added HTML grabbed with jQuery.
In angular 1.2 this does no longer work:(
Error: [ng:btstrpd] App Already Bootstrapped with this Element '' http://errors.angularjs.org/1.2.0-rc.2/ng/btstrpd?p0=%3Cdiv%20ng-controller%3D%22AfterCtrl%22%3E
I am getting this error which I understand, but I don't know how to solve it.
What I need to be able to do is load angularized-html and then make angular aware of it.
Here is a plunker to illustrate it: http://plnkr.co/edit/AHMkqEO4T6LxJvjuiMeT?p=preview
I will echo what others have mentioned: this kind of thing is generally a bad idea, but I also understand that you sometimes have to work with legacy code in ways you'd prefer not to. All that said, you can turn HTML loaded from outside Angular into Angular-bound views with the $compile service. Here's how you might rewrite your current example to make it work with $compile:
// We have to set up controllers ahead of time.
myApp.controller('AfterCtrl', function($scope) {
$scope.loaded = 'Is now loaded';
});
//loads html and afterwards creates a controller
$('button').on('click', function() {
$.get('ajax.html', function(data) {
// Get the $compile service from the app's injector
var injector = $('[ng-app]').injector();
var $compile = injector.get('$compile');
// Compile the HTML into a linking function...
var linkFn = $compile(data);
// ...and link it to the scope we're interested in.
// Here we'll use the $rootScope.
var $rootScope = injector.get('$rootScope');
var elem = linkFn($rootScope);
$('.content').append(elem);
// Now that the content has been compiled, linked,
// and added to the DOM, we must trigger a digest cycle
// on the scope we used in order to update bindings.
$rootScope.$digest();
}, 'html');
});
Here is an example: http://plnkr.co/edit/mfuyRJFfA2CjIQBW4ikB?p=preview
It simplifies things a bit if you can build your functionality as a directive instead of using raw jQuery--you can inject the $compile and $rootScope services into it, or even use the local scope inside the directive. Even better if you can use dynamic binding into an <ng-include> element instead.
Your approach doesn't seem right. You are usinging jQuery and Angular together in an inappropriate way that is likely to have conflicts.
Angular's built in template support is the best way to do this either using ng-include or you can use Angular's routing and along with ng-view. The documentation is here:
http://docs.angularjs.org/api/ng.directive:ngInclude
http://docs.angularjs.org/api/ngRoute.directive:ngView
The simplest possible thing would be to just set the ng-include to the url string:
<div ng-include="'ajax.html'"></div>
If you actually need it to load dynamically when you do something then this is a more complete solution for you:
http://plnkr.co/edit/a9DVEQArS4yzirEQAK8c?p=preview
HTML:
<div ng-controller="InitCtrl">
<p>{{ started }}</p>
<button ng-click="loadTemplate()">Load</button>
<div class="content" ng-include="template"></div>
</div>
Javascript:
var myApp = angular.module('myApp', [])
.controller('InitCtrl', function($scope)
{
$scope.started = 'App is started';
$scope.loadTemplate = function() {
console.log('loading');
$scope.template = "ajax.html";
}
}).controller('AfterCtrl', function($scope)
{
$scope.loaded = 'Is now loaded';
});
Loading an AngularJS controller dynamically
The answer to this question fixed my problem. Since I need to create the controllers after the content was added to the DOM. This fix requires me too register controllers after I have declared it. If someone has an easier solution pleace chip in.
One other gotcha that leads to this Bootstrapping error is the nginclude or ngview scenarios where your dynamic html includes script references to angular js.
My html below was causing this issue when it got injected into an existing Angular page. The reference to the angular.min.js caused Angular to rebootstrap:
<div id="fuelux-wizard" class="row-fluid" data-target="#step-container">
<ul class="wizard-steps">
<li data-target="#step1">
<span class="step">1</span>
<span class="title">Submit</span>
</li>
<li data-target="#step2">
<span class="step">2</span>
<span class="title">Approve</span>
</li>
<li data-target="#step3">
<span class="step">3</span>
<span class="title">Complete</span>
</li>
</ul>
</div>
<script src="/Scripts/Angular/angular.min.js"></script>
<script type="text/javascript">
angular.element('#requestMaster').scope().styleDisplayURL();
</script>
I am a rookie Grails user and I am completely new to AJAX. I am not exactly grasping the concept of AJAX, and the material online is fragmented.
From my understanding, in grails if I wanted to execute a method in one of my controllers when a part of my HTML doc loads I could simply use something along the lines of
<div onload="${remoteFunction(action:"foo", update:"foo"...)}" ...>
How is the response from the call to the hypothetical foo returned and how can I access it in a js function?
Can I return an object from a class I created from my hypothetical foo action?
On the return of the foo action you can put simple html as text or render some objects that can be used in the view.
Here you have all the info about the Controller "render"
http://grails.org/doc/latest/ref/Controllers/render.html
You can have a that will be update with that data and work there with it. Then you can access to the Html and data inside that "foo" div with javascript like you usually do.
For example:
Controller.groovy
// renders text to response
render '<div id="bar" onclick="alert($('bar').val())>some text</div>'
View.gsp
//Makes the call and updates foo
<div onload="${remoteFunction(action:"foo", update:"foo"...)}" ...>
<div id="foo" name="foo"></div>
Output
<div onload="theAjaxJavascriptFunctionThatGrailsWillInject" ...>
<div id="foo" name="foo">
<div id="bar" onclick="alert($('bar').val())">some text</div>
</div>
I you return some object from the Controller.grooy then you have to treat it like this in you View.gsp
//Makes the call and updates foo
<div onload="${remoteFunction(action:"foo", update:"foo"...)}" ...>
<div id="foo" name="foo">
${myObject.theValueIWant}
</div>
I added a javascript alert but you can do it the way you like, there are lots of ways to do it.
Hope it helps :)