So with regular jquery, I can do:
var = variable.data('data-attribute');
What would be the correct way to do this with Angular?
There are various ways to do this, you should even use the jQuery command that you wrote above to read data attribute. Do this inside a controller and you are OK. I used jQuery for readability sake, you should use jQueryLite or even Vanilla Javascript.
In your controller:
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.expando = $( "#testId" ).data( "test" );
});
and in your view:
<p id="testId" data-test="this is expando attr">Hello {{name}}, {{expando}}!</p>
See the plunker for example.
This hovewer (maybe) is not the best solution! More suitable should be the use of a directive. Probably that attribute is something that you read inside a Javascript function to do something with the DOM, and the directives are the angular way to reuse component that alter the DOM.
So, your question can't have a better answer until you don't specify what's the use of that data-attribute.
Related
I am learning Angular and need to create some custom filters.
Do I create one filters.js file and put all my filters in there similar to all my reusable factory.js?
Eg: I have a utilsFactory.js and I put reusable functions in here.
Does this then get injected into the controllers? Or is this loaded in the $rootscope somewhere?
I have seen many examples on how to create them but not how to store and manage them and how to access them properly
filter.js
angular.module('achApp', [])
.filter('myUpperCase', function(){
return function(value){
return String(value).toUpperCase();
}
});
Controller
(function(){
var DevbController = function($scope, utilsFactory, $filter){
$scope.greeting = 'hello';
};
DevbController.$inject = ['$scope', 'utilsFactory', '$filter'];
angular.module('achApp')
.controller('DevbController', DevbController)
}());
Filters are generally created for use in your templates (HTML). For example:
<p>{{somethingOnScope | myFilter}}</p>
You can use them in your JS files by injecting $filter, and then getting a reference to your filter like: $filter('myFilter');
You can see usage examples in the filter documentation:
https://docs.angularjs.org/api/ng/filter/filter
Where and how exactly you register your filters is a matter of style. I personally try to follow john papa's advice and create only one injectable item per file.
On another subjective note, I prefer to minimize the use of filters, especially custom ones. If you want to use them primarily in your JS, a service/factory feels cleaner to me, and if you're tempted to create one for use in your templates, often you can instead just change the thing before putting it on the scope.
For all new work I use the ControllerAs syntax so that I can refer to the controller as vm within my views. Which is great.
However I still a large portion of old code that uses the $scope syntax. In particular one of my directives uses ng-click="groupClick" which would usually call the groupClick function on the $scope of the parent controller. This view is being built with the ContollerAs syntax so this doesn't work anymore.
The existing code looks like this (simplified)
<span>{{vm.somePropertyOnController}}</span>
<!-- This is provided by a template I cannot change -->
Test
How can I get the <a> (which is provided via a template I cannot change) to call vm.groupClick. I'd ideally not like to add $scope to my controller simply to get groupClick working
My controller code:
var vm = this;
vm.somePropertyOnController = "yay vm is cool";
// i really dont want to do this! dont make me!
$scope.groupClick = function (group) { console.log("test"); }
Change your groupClick function:
vm.groupClick = groupClick
function groupClick() {
console.log('test')
}
In the view, you would have to use the controllerAs syntax:
<div ng-controller="controllerName as ctrl">
<span>{{ctrl.somePropertyOnController}}</span>
Test
</div>
In my application I would like to preserve the option of using plain controllers for certain sections of code - as opposed to creating directives for one-off things that will never be re-used.
In these cases I often want to publish some data from the controller to be used in the contained section. Now, I am aware that I could simply bind items in the controller's scope, however I'd like to specify the "model" location explicitly just to make the code more maintainable and easier to read. What I'd like to use is ng-model as it would be used on a custom directive, but just along side my plain controller:
<div ng-controller="AppController" ng-model='fooModel'>
{{fooModel}}
</div>
However I can see no way to get a reference to the generated ngModelController without using a directive and the 'require' injection.
I am aware that I could make my own attribute fairly easily by injecting the $attr into my controller and do something like:
<div ng-controller="AppController" my-model='fooModel'>
{{fooModel}}
</div>
In which case I just manually take or parse the myModel value and stick my model into the $scope under that name. However that feels wrong in this case - I really only need one "model" for a controller and I'd prefer not to have to add this boilerplate to every controller when ngModel exists. (It's the principle of the thing!)
My questions are:
1) Is there some way to use ngModel along with a plain controller to get the effect above?
2) I have been trying to figure out where ngModelControllers are stored so that I could look at the situation in the debugger but have not been able to find them. When using an ngModel directive should I see these in the scope or parent scope? (Where do they live?!?)
UPDATE: As suggested in answers below $element.controller() can be used to fetch the controller. This works (http://plnkr.co/edit/bZzdLpacmAyKy239tNAO?p=preview) However it's a bit unsatisfying as it requires using $evalAsync.
2) I have been trying to figure out where ngModelControllers are stored so that I could look at the situation in the debugger but have not been able to find them. When using an ngModel directive should I see these in the scope or parent scope? (Where do they live?!?)
The answer depends slightly on where you want to access the controller from.
From outside the element with ng-model
It requires "name" attributes on both the element with the ng-model attribute, and a parent form (or ngForm). So say you have the form with name myForm and the element with ng-model attribute with name myInput, then you can access the ngModelController for myFoo from the parent scope as myForm.myInput. For example, for debugging purposes:
<p>myFoo: {{myForm.myInput.$modelValue}}<p>
<form name="myForm">
<div ng-controller="InnerController" name="myInput" ng-model="model.foo"></div>
</form>
as can be seen at http://plnkr.co/edit/IVTtvIXlBWXGytOEHYbn?p=preview
From inside the element with ng-model
Similar to the answer from #pixelbits, using $evalAsync is needed due to the order of controller creation, but you can alternatively use angular.element.controller function to retrieve it:
app.controller('InnerController', function($scope, $element) {
$scope.$evalAsync(function() {
$scope.myModelController = $element.controller('ngModel');
});
});
Used, inside the controller to view it, for debugging purposes, as:
<div ng-controller="InnerController" ng-model="model.foo">
<p>myFoo: {{myModelController.$modelValue}}<p>
</div>
As can be seen at http://plnkr.co/edit/C7ykMHmd8Be1N1Gl1Auc?p=preview .
1) Is there some way to use ngModel along with a plain controller to get the effect above?
Once you have the ngModelController inside the directive, you can change its value just as you would were you using a custom directive accessing the ngModelController, using the $setViewValue function:
myModelController.$setViewValue('my-new-model-value');
You can do this, for example, in response to a user action that triggers an ngChange handler.
app.controller('InnerController', function($scope, $element) {
$scope.$evalAsync(function() {
$scope.myModelController = $element.controller('ngModel');
});
$scope.$watch('myModelController.$modelValue', function(externalModel) {
$scope.localModel = externalModel;
});
$scope.changed = function() {
$scope.myModelController.$setViewValue($scope.localModel);
};
});
Note the extra watcher on $modelValue to get the initial value of the model, as well as to react to any later changes.
It can be used with a template like:
{{model.foo}}
<div ng-controller="InnerController" ng-model="model.foo">
<p><input type="text" ng-model="localModel" ng-change="changed()"></p>
</div>
Note that this uses ngChange rather than a watcher on localModel. This is deliberate so that $setViewValue is only called when the user has interacted with the element, and not in response to changes to the model from the parent scope.
This can be seen at http://plnkr.co/edit/uknixs6RhXtrqK4ZWLuC?p=preview
Edit: If you would like to avoid $evalAsync, you can use a watcher instead.
$scope.$watch(function() {
return $element.controller('ngModel');
}, function(ngModelController) {
$scope.myModelController = ngModelController;
});
as seen at http://plnkr.co/edit/gJonpzLoVsgc8zB6tsZ1?p=preview
As a side-note, so far I seem to have avoided nesting plain controllers like this. I think if a certain part of the template's role is to control a variable by ngModel, it is a prime candidate for writing a small directive, often with an isolated scope to ensure there are no unexpected effects due to scope inheritance, that has a clear API, and uses require to access the ngModelController. Yes, it might not be reused, but it does help enforce a separation of responsibilities between parts of the code.
When you declare directives on an element:
<div ng-controller="AppController" ng-model='fooModel'>
{{fooModel}}
</div>
You can retrieve the controller instance for any directive by calling jQlite/jQuery $element.data(nameOfController), where nameOfController is the normalized name of the directive with a $ prefix, and a Controller suffix.
For example, to retrieve the controller instance for the ngModel directive you can do:
var ngModelController = $element.data('$ngModelController');
This works as long as the ngModel directive has already been registered.
Unfortunately, ngController executes with the same priority as ngModel, and for reasons that are implementation specific, ngModel is not registered by the time that the ngController function executes. For this reason, the following does not work:
app.controller('ctrl', function ($scope, $element) {
var ngModelController = $element.data('$ngModelController');
// this alerts undefined because ngModel has not been registered yet
alert(ngModelController);
});
To fix this, you can wrap the code within $scope.$evalAsync, which guarantees that the directives have been registered before the callback function is executed:
app.controller('ctrl', function ($scope, $element) {
$scope.$evalAsync(function() {
var ngModelController = $element.data('$ngModelController');
alert(ngModelController);
});
});
Demo JSFiddle
I am looking at some samples of how controllers work in angular and I see two ways of declaring them, one with just controller name and one with "as somename". Examples that use ng-controller = "myController" take a $scope as dependency when defining controller.
Then model is then set on the $scope, something like this
$scope.mymodel = somevalue;
Example that uses "as" syntax such as ng-controller = "MyControler as vm" never uses $scope when setting up the model but ratther assigns it to "this" and binds using {{vm.something}}.
in controller:
var vm =this;
vm.something = somevalue;
How is that working in second example? Is that new way in latest version?
Using the "as" syntax exposes your entire controller to your view. In my opinion, that is a bad practice. Although I'm not sure which one is better performance wise, but using 'this' in javascript already has plenty of issues of its own, and I don't recommend adding another meaning to 'this'. So I would stick to $scope (since that is what they're using in the docs as well).
See this topic if you want to know more context about how the 'as' syntax work: 'this' vs $scope in AngularJS controllers
Using AngularJS and UI Bootstrap, I want to dynamically add alerts to DOM. But if I dynamically add an <alert> element to DOM, it's not compiled automatically. I tried to use $compile but it doesn't seem to understand tag names not present in core AngularJS. How can I achieve this? Is it even the right way to "manually" add elements to DOM in services?
See Plunker. The alert in #hardcodedalert is compiled and shown correctly but the contents of #dynamicalert are not being compiled.
Edit:
I'd later want to have alerts shown on different context and locations on my web page and that's why I created a constructor function for the alerts, to have a new instance in every controller which needs alerts. And just for curiosity's sake, I was wondering if it's possible to add the <alert> tags dynamically instead of including them in html.
I've updated your plunker to do what you're trying to do the "angular way".
There are a few problems with what you were trying to do. The biggest of which was DOM manipulation from within you controller. I see you were trying to offset that by handling part of it in the service, but you were still referencing the DOM in your controller when you were using JQuery to select that element.
All in all, your directives weren't compiling because you're still developing in a very JQuery-centric fashion. As a rule of thumb you should let directives handle the adding and removing of DOM elements for you. This handles all of the directive compiling and processing for you. If you add things manually the way you were trying, you will have to use the $compile provider to compile them and run them against a scope... it will also be a testing and maintenance nightmare.
Another note: I'm not sure if you meant to have a service that returned an object with a constructor on it, so I made it just an object. Something to note is that services are created and managed in a singleton fashion, so every instance of that $alertService you pass in to any controller will be the same. It's an interesting way to share data, although $rootScope is recommended for that in most cases.
Here is the code:
app.factory('alertservice', [function() {
function Alert() {
this.alerts = [];
this.addAlert = function(alert) {
this.alerts.push(alert);
};
}
return {
Alert: Alert
};
}]);
app.controller('MainCtrl', function($scope, alertservice) {
var myAlert = new alertservice.Alert();
$scope.alerts = myAlert.alerts;
$scope.add = function() {
myAlert.addAlert({"text": "bar"});
};
});
Here are the important parts of the updated markup:
<body ng-controller="MainCtrl">
<div id="dynamicalert">
<alert ng-repeat="alert in alerts">{{alert.text}}</alert>
</div>
<button ng-click="add()">Add more alerts...</button>
</body>
EDIT: updated to reflect your request