I relatively new to AngularJS, and also brushing up on my JS in the process, so this may come across as a very basic question, but here goes nothing. I would like to separate my code out, i.e. the logic from the presentation, since I would like to follow the MVC guidelines.
So, the problem I have is that I end up having to write a lot of code in my HTML, and I really do not like this since, it gets hard to debug later on, and this code is included inside ng-repeat.
So, without much further-ado, here is the code, for reference, I please check this, as it has line numbers to the side.:
HTML
<!doctype html>
<html lang="en-US" ng-app>
<head>
<meta charset="UTF-8">
<title>ng-click</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" type="text/css" href="style.css" media="all"/>
</head>
<body>
<!--Search div-->
<div class="search">
<label for="findName" >
Find your name:
<input type="search" name="userSearch" id="angularUserSearch" ng-model="search"/>
</label>
</div>
<!--List of results-->
<div ng-controller="List">
<ul>
<!--Can also work with objects-->
<li ng-repeat="person in people | filter:search">
{{ person.name }} -> {{ person.age }}
</li>
</ul>
<div>
<label for="">
Name:
<input type="text" name="new_name" id="new_name" ng-model="new_name"/>
</label>
<br>
<label for="">
Age:
<input type="text" name="new_age" id="new_age" ng-model="new_age"/>
</label>
<br>
<button ng-click="add()">Add</button>
</div>
</div>
<!--Scripts-->
<script type="text/javascript" src="angular_1.0.7.js"></script>
<script type="text/javascript" src="myscript.js"></script>
</body>
</html>
The styling is irrelevant. So, I leave it out.
JavaScript
var List = function ($scope) {
// Making the list called names
$scope.people = [
{name: "Harold", age:"20"},
{name: "Jessie", age:"34"},
{name: "Samantha", age:"18"},
{name: "March", age:"40"},
{name: "Scott", age:"44"},
{name: "David", age:"28"},
{name: "Dan", age:"28"}
];
$scope.add = function () {
$scope.people.push({
name: $scope.new_name,
age: $scope.new_age
});
$scope.new_name = "";
$scope.new_age = "";
};
};
So, what I would like to do, is simply give ng-repeat (on line 22) a function, that is declared in my JS, so that I do not have to put in any business logic inside the code. So for example, I would like something like this:
<li ng-repeat="function_that_returns_person_objects()">
How can I achieve this?
It makes sense to put page logic on the template like the comprehension expression in the ng-repeat. This is not the business logic, it is the logic for rendering the page. And it is more elegant than implementing it using an function.
ng-repeat only takes an expression but you should be able to use a function as a part of the expression like this,
<li ng-repeat="data in myFunction()">
so you can simplify a bit if you need to preprocess the data source. But I don't think you can use a pure function in the ng-repeat as what you said. Hope it helps.
I don't quite understand why you need the function_that_returns_person_objects again. Angular's binding will take care of ensuring your view stays up to date.
Here's a demonstration, and here's the source for it.
If you have a complex filter function then just write it 'aside' like this : http://docs.angularjs.org/guide/dev_guide.templates.filters.creating_filters
But when you consume it, it's right from the view, as you want to repeat an view partial following the value returned by it.
Related
I read like AngularJS has a context where it has deployed watchers and digestive loop. So if any variable is updated from either front-end or backend-end, these above-mentioned components will help to make sure all the respective fields where this variable is involved will be updated.
So I thought of testing this, so created a sample multiple by 2 like below
<!DOCTYPE html>
<html ng-app="myapp">
<head>
<title>Parcel Sandbox</title>
<meta charset="UTF-8" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.js"></script>
</head>
<body>
<div id="app"></div>
<script>
</script>
<div class="container" ng-controller="maincontroller">
<label id="input_value">Please enter number </label>
<input type="text" id="input_value" ng-model="number" />
<p>You have entered : {{ number }}</p>
<p>
Multiplied by 2 : {{ multipleBy2 }}
</p>
</div>
<script src="src/index.js"></script>
</body>
</html>
and my index.js code is as below
var myapp = angular.module("myapp", []);
var maincontroller = function ($scope) {
$scope.number = "";
$scope.multipleBy2 = $scope.number * 2; // this is returning zero at the beginning of the page.
};
myapp.controller("maincontroller", maincontroller);
is it wrong if I expect that, the moment number input field received 2, I should see Multiplied by 2 : as 4 ?
Am I missing anything here?
In order for this to work as you're expecting out of the box, AngularJS would need to do one of two things:
Reprocess everything on every change, which would be a performance nightmare, or
Be smart enough to know when certain scope properties are dependent on the value of other scope properties and only reprocess those relevant properties, which would be massively complex.
The easy way to render the result of something that always needs to run fresh is to make it the result of a function instead of a property.
$scope.multipleBy2 = () => $scope.number * 2;
While AngularJS expressions bound to function results will always re-run on a digest loop, it won't actually re-render the DOM of the corresponding element unless the value changes in between digests, so thankfully there is some optimization that takes place.
As a best practice, these functions should should not mutate the state of the $scope when run, otherwise you could get into infinite digest loop issues.
$scope.multipleBy2 = () => $scope.number++ * 2;
// infinite $digest loop error
This question already has an answer here:
ng-repeat runs twice (Angular) [duplicate]
(1 answer)
Closed 5 years ago.
For a setup like this:
HTML
<li ng-repeat="item in data.checkList">
<i ng-class="faClass(item.status)"> </i> <span>{{item.Name}}</span>
</li>
JS
$scope.data.checkList = [
{'status':'COMPLETED', 'name':'Task 1'},
{'status':'NOT STARTED', 'name':'Task 2'}
];
$scope.faClass = function(status){
console.log(status);
};
BROWSER CONSOLE
COMPLETED
NOT STARTED
COMPLETED
NOT STARTED
Is this behavior expected? How do I solve it and avoid the duplication?
JSFIDDLE
Demo
Yes It's normal, basically AngularJS uses dirty-checking to figure out what (and
when) to repaint.
How does data binding work in AngularJS?
var app = angular.module('myApp', []);
app.controller('AppCtrl', function($scope) {
$scope.data ={};
$scope.data.checkList = [
{'status':'COMPLETED', 'name':'Task 1'},
{'status':'NOT STARTED', 'name':'Task 2'}
];
$scope.faClass = function(status){
console.log(status);
};
});
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<div ng-app="myApp" ng-controller="AppCtrl">
<div class="form-group">
<li ng-repeat="item in data.checkList">
<i ng-class="faClass(item.status)"> </i> <span>{{item.name}}</span>
</li>
</div>
</div>
</body>
</html>
This is expected behavior of AngularJS. AngularJS uses a digest cycle to check if there any changes to variables. Therefore, as part of the digest cycle's dirty checking, it will check a few times to ensure there have been no changes to the variable. See more in the 'Scope Life Cycle' section: $scope.
If you want the value to only be bound once you can prepend :: to your function call. Note: It's still going to be called twice, but it will be called no more than twice using ::.
<i ng-class="::faClass(item.status)"> </i> <span>{{item.Name}}</span>
I'm following the tutorial here: http://www.youtube.com/watch?v=iUQ1fvdO9GY while learning yeoman and angular.
I've gotten to here http://www.youtube.com/watch?v=iUQ1fvdO9GY#t=266 and where his example works, mine does not due to some scope problems.
main.html
<div class="jumbotron">
<h2>My Todos:</h2>
<p ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span>{{todo.text}}</span>
</p>
<form ng-submit="addTodo()">
<input type="text" ng-model="newTodoText" size="20">
<input type="submit" class="btn btn-primary" value="add">
</form>
</div>
main.js
'use strict';
var myApp = angular.module('todoAppApp');
myApp.controller('MainCtrl', ['$scope', function ($scope) {
$scope.todos = [
{text: 'Item 1', done: false},
{text: 'Item 2', done: true},
{text: 'Item 3', done: false}
];
$scope.addTodo = function() {
$scope.todos.push({text: $scope.newTodoText, done: false});
$scope.newTodoText = '';
};
}]);
For some reason, though, the newTodoText variable is in a scope that is child to the $scope in main.js. This is confirmed using Batarang. I can't post a picture due to lack of rep, but in Batarang, there's:
Scope001 > Scope002 > Scope003(which is the $scope I have access to in the js) > Scope004 > {Scopes for each of the todos}
Scope003 has the original todos array and the addTodo() function. Scope004 has the newTodoText text when you type into the input form. On clicking add, addTodo() is correctly called, but $scope doesn't contain newTodoText because it's in Scope004.
I'm obviously missing something simple here due to my newness to the framework and the practically barebones implementation here. My Google-fu has turned up few results.
EDIT:
Ok, so in index.html, it contains the line
<div class="container" ng-include="'views/main.html'" ng-controller="MainCtrl"></div>
which includes main.html. I've replaced that line with the literal contents of main.html enclosed in the div
<div class="container" ng-controller="MainCtrl">
<!-- contents of main.html from above -->
</div>
And magically my scope problems are solved. Why does including main.html from a separate file mess with the scope?
EDIT #2
Cool, I figured it out.
Turns out that ng-include creates a new scope, which is contrary to what I thought it did (I had assumed that it was equivalent to a literal html injection). So I just moved the ng-controller="MainCtrl" from the .container div in index.html (Scope003) to the .jumbotron div within main.html.
Thanks for the help, and I'm a lot more knowledgeable about scope now!
ng-include creates a new scope. So I moved the ng-controller="MainCtrl" from the .container div in index.html (Scope003) to the .jumbotron div within main.html.
I need a very easy thing and looking on the web the solutions founded tell me that my code is right. But clearly isn't.
I just need to display how many notes(models) I have in my app:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> Notes and bookmarks </title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<script type="text/x-handlebars" data-template-name="index">
<div class="wrap">
<div class="bar">
<input type="text" class="search" />
<div class="bar-buttons">
<button> NEW </button>
<button> HOME </button>
</div>
</div>
<aside>
<h4 class="all-notes">All Notes {{all}}</h4>
{{#each item in model}}
<li>
{{#link-to 'note' item}} {{item.title}} {{/link-to}}
</li>
{{/each}}
</aside>
{{outlet}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="main">
<section>
<h2>Hellooo</h2>
</section>
</script>
<script type="text/x-handlebars" data-template-name="note">
<section>
<div class="note">
{{#if isEditing}}
<h2 class="note-title input-title"> {{edit-input-note value=title focus-out="modified" insert-newline="modified"}} </h2>
<p class="input-body"> {{edit-area-note value=body focus-out="modified" insert-newline="modified"}} </p>
{{edit-input-note value=url focus-out="modified" insert-newline="modified"}}
{{else}}
<h2 {{action "editNote" on="doubleClick"}} class="note-title" > {{title}} </h2>
<button {{action "removeNote"}} class="delete"> Delete </button>
<p {{action "editNote" on="doubleClick"}}> {{body}} </p>
{{input type="text" placeholder="URL:" class="input" value=url }}
{{/if}}
</div>
</section>
</script>
<script src="js/libs/jquery-1.9.1.js"></script>
<script src="js/libs/handlebars-1.0.0.js"></script>
<script src="js/libs/ember-1.1.2.js"></script>
<script src="js/libs/ember-data.js"></script>
<script src="js/app.js"></script>
<script src="js/router.js"></script>
<script src="js/models/note_model.js"></script>
<script src="js/controllers/note_controller.js"></script>
<script src="js/controllers/notes_controller.js"></script>
<script src="js/views/note_view.js"></script>
</body>
</html>
My model:
Notes.Note = DS.Model.extend ({
title: DS.attr('string'),
body: DS.attr('string'),
url: DS.attr('string')
});
Notes.Note.FIXTURES = [
{
id: 1,
title: 'hello world',
body: 'ciao ciao ciao ciao',
url: ''
},
{
id: 2,
title: 'javascript frameworks',
body: 'Backbone.js, Ember.js, Knockout.js',
url: '...'
},
{
id: 3,
title: 'Find a job in Berlin',
body: 'Monster, beralinstartupjobs.com',
url: '...'
}
]
And my notes controller:
Notes.NotesController = Ember.ArrayController.extend ({
all: function () {
var notes = this.get('model');
return notes.get('lenght');
}.property('model')
});
I think that's all the important code for make this work but if you need others part I will add.
Why i can't see the number of my notes in {{all}} ?
In your notes.hbs template you should be able to do {{length}}.
I made a few adjustments to the code that you provided and got it to work the way that i think you expected it to work. I am going to try to explain where I think you may have been sent for a loop as it relates to your App. Ember takes some getting use to for sure.
One thing that is important is that Ember uses naming conventions to help you wire up and associate your Router, Routes, Controllers, Templates correctly with each other and also help you to look at code and know what Ember expects. Ember also gives you free stuff, yaaay for free stuff.
So when Ember boots up you get some default assets for free the ApplicationRoute, ApplicationController, and the application template these are always present in Ember even if you never explicitly define them. However if you need to use them just define them and add whatever code. In addition to those you get an IndexRoute, IndexController and index template which are present at the top of Ember like those other assets.
Remember the application template, which is at the highest level of the app, think of it as the parent of all templates. Other templates will be put into it and rendered, including the index template that you got for free. Now this is where things get a little tricky.
If you define a router like this in Ember for example. You still have that index template which you can use as you were and you also now have a template called note.
App.Router.map(function() {
this.resource('note');
});
Can you guess the names for the Controller and Route associated with the note resource using Ember naming conventions? I'll leave that for homework. Now when Ember boots with this Router if you defined a index template (just like you were) it will automatically be pushed into the application templates {{outlet}}and rendered. A note on the application template if all its doing is basically being and 'outlet' to render stuff Ember will do that just fine by default.
Behind the scenes the default application template may look something like this. I just put the <script> tags to highlight its the application template.
<script type="text/x-handlebars">
{{outlet}}
</script>
The template without a data-template-name is used as the application template. You can put in data-template-name="application" if you like but its not necessary unless you are using build tools. Now back to the Router stuff.
If you define a Router like this something happens that is important to realize.
App.Router.map(function() {
this.resource('note', { path: '/'});
});
By adding {path: '/'} in that resource you are overriding the /, which is the based url of you application. Now '/' is not associated with index template that you got for free its the note template. When Ember boots it will automatically push the note template into the application outlet.
Moving along, in your code you had few other things that were also conflicting. Remember with naming conventions if ask Notes.NotesController for something Ember by default will look for a Notes.NotesRoute and a notes template, attention to the pluralization, but in your code your code you have a note template.
The other conflict is with the index template, again with the naming conventions Ember is going to look for the IndexController for the {{all}} property and IndexRoute to the supply the model. In your code its on the Notes.NotesController.
And finally dont forget if you are using and adapter to define it:
Notes.ApplicationAdapter = DS.FixtureAdapter.extend({});
Sorry for the long answer but i hope it clear up things
Here the jsBin.
http://emberjs.jsbin.com/UPaYIwO/7/edit
Happy Coding!!
PS. You could have put {{model.length}} in the template and it would have accomplished the same thing. But in Ember there is a few ways to do the same thing.
There is also a typo:
return notes.get('lenght');
Lenght should be length;
return notes.get('length');
I want to be able to use multiple ng-app="{angular.module}" directives on one page. I want to do this to make the bits of my app more modular. I figure, if I can create angular modules and plug several of them into one document, I could take those modules and plug them into other projects easily.
I have seen people say that you can only use one ng-app directive on your page... is this true? Is it most accurate to say, "one ng-app directive per view"?
I hope this is not the case, or if it is the case that there is still a best way to achieve a high degree of abstract modularity.
Here are my modules/apps and their controllers...
var searchModj = angular.module('searchModule', []);
var controllers = {};
controllers.SearchList = function ($scope){
$scope.coworkers = [
{name: 'Joe Bob', city: 'Ukrainia'},
{name: 'Adam Blobnovski', city: 'Logan' },
{name: 'Carlos Sanchez', city: 'Deerbushle'},
{name: 'Martin Kellerweller', city: 'Uptown'},
{name: 'John Doe', city: 'New York City'}
];
};
searchModj.controller(controllers);
var fruitModj = angular.module('fruiter', []);
controllers.SomeFruit = function ($scope) {
$scope.fruits = ['apple', 'banana', 'pear'];
};
fruitModj.controller(controllers);
Ok, now here is the relevant part of my markup...
<div ng-app="searchModule">
<div ng-controller="SearchList">
Name:
<br/>
<input type="text" ng-model="name" />
<br/>
<ul>
<li ng-repeat="coworker in coworkers | filter:name">{{ coworker.name }} - {{ coworker.city }}</li>
</ul>
<p>You are searching for <em>{{ name }}</em></p>
</div>
</div>
<div ng-app="fruiter">
<div ng-controller="SomeFruit">
<ul>
<li ng-repeat="fruit in fruits">{{ fruits }}</li>
</ul>
</div>
</div>
I think because it comes first in the document, my "searchModule" app works and the second app does not. When I comment out the first app, the second works. So it looks like I'm confirming my most unfortunate suspicions. Regardless... if this is the case, then what is the best approach I can bear in mind to make the functionality on my projects as modular as possible?
you only want one ng-app on a page, but you can insert your other modules as dependencies of the main ng-app module.
var app=angular.module('myNgAppName', ['searchModule']);
This will expose any directives , controllers etc you have in your 'searchModule'
The limitations of the ngApp directive is just that, limitations of the directive, not AngularJS itself. Angular allow you to associate modules with multiple elements in a page, it even allows you to associate more than one module with each element.
Referencing other modules from you module will work. Another approach that will work is using the angular.bootstrap() method. See: https://stackoverflow.com/a/18583329/984780
Finally you can create a directive that works like ngApp without it's limitations. It would work exactly the way it does in your markup code. That's what I did you can get the code here:
http://www.simplygoodcode.com/2014/04/angularjs-getting-around-ngapp-limitations-with-ngmodule/
The directive is called ngModule. Here's a code sample:
<!DOCTYPE html>
<html>
<head>
<script src="angular.js"></script>
<script src="angular.ng-modules.js"></script>
<script>
var moduleA = angular.module("MyModuleA", []);
moduleA.controller("MyControllerA", function($scope) {
$scope.name = "Bob A";
});
var moduleB = angular.module("MyModuleB", []);
moduleB.controller("MyControllerB", function($scope) {
$scope.name = "Steve B";
});
</script>
</head>
<body>
<div ng-modules="MyModuleA, MyModuleB">
<h1>Module A, B</h1>
<div ng-controller="MyControllerA">
{{name}}
</div>
<div ng-controller="MyControllerB">
{{name}}
</div>
</div>
<div ng-module="MyModuleB">
<h1>Just Module B</h1>
<div ng-controller="MyControllerB">
{{name}}
</div>
</div>
</body>
</html>
Yes you only want one ng-app per page, but you can create other modules and declare them as dependencies of your main app.
var app=angular.module('appModule'); //resuable module
var app=angular.module('myApp', ['appModule']); //in the HTML ng-app="myApp"
So you can put re-usable stuff in appModule and use it in other projects. For example, I like to put my routing logic (i.e. re-routing users depending on their access levels) in a module that I reuse in other projects.
Note: You might want to look into $provider http://docs.angularjs.org/api/AUTO.$provide if you want to use data from your re-usable ("appModule") module inside the config method of your ng-app ("myApp") module.