How to inject service dependencies from templates into controllers? - angularjs

I have the following template.html:
<html>
...
<my-tab></my-tab>
...
</html>
Tag <my-tab> is represented by tabTemplate.html and managed by a MyTab controller with the following constructor:
constructor(
private firstService: FirstServiceClass,
){
this.doSomething(); // this fuction uses firstService
}
FirstServiceClass - a custom class.
I have another tags like <my-tab2>, <my-tab3> etc. They are managed by MyTab2, MyTab3 controllers.
The code of these controllers is almost the same, the difference between them is a parameter in a constructor.
I need to remove copypaste.
Is it possible to specify this parameter somehow?

Use component bindings:
app.component("myTab", {
bindings: {
tab: "<"
},
controller: "myTabController",
templateURL: "myTab.html"
}
Usage:
<my-tab tab="0"></my-tab>
<my-tab tab="1"></my-tab>
class myTabController {
constructor (firstService) {
//...
}
$onInit() {
console.log(this.tab);
//...
}
}
For more information, see
AngularJS Developer Guide - Component-based Architecture
for tab="0" it's needed to use myTabController constructor(firstService). for tab="1" it's needed to use myTabController constructor(secondService). Is it possible?
Use the $injector service to inject different services:
app.component("myTab", {
bindings: {
tab: "<",
service: "#"
},
controller: "myTabController",
templateURL: "myTab.html"
}
class myTabController {
constructor ($injector) {
this.$injector = $injector;
}
$onInit() {
this.tabService = this.$injector.get(this.service);
//...
}
}
Usage:
<my-tab tab="0" service="firstService"></my-tab>
<my-tab tab="1" service="secondService"></my-tab>
For more information, see
AngularJS $injector Service API Reference - get

you can create a directive for my-tab that would be better for binding new temple for this directive and use a model controller for logic.

////This html will be inside parents controller
<div class="tabClass hide">
<my-tab></my-tab>
</div>
////////////////////
'use strict';
angular.module('demoApp').directive('myTab', function () {
return {
templateUrl: "~/MyTab.html",//Create a template for directive view named like MyTab
restrict: "E",
controller: function ($scope, $routeParams, $http, $filter, $modal ) {
//essential code
}
};
});
//and finally add directive referance to index file or where you referances other js file in your project

Related

Refresh the data in controller when scope gets changed in AngularJS

I'm pretty new with angular and I've read a lot of threads here and googled this topic but I cannot get a clear answer. what i am really trying to achieve is. lets suppose I have a controller A, this is a actual source for data. I passed it to one directive through binding it to a HTML. From this directive I am acually getting the source at another controller.
So I need to find out the way where I can change the data of controller when the data of controller A gets changed.
Controller A
angular.module('page.leadAndOpportunity.ctrl', []).controller('LeadAndOpportunityController', ['$scope', '$rootScope', '$timeout', function ($scope, $rootScope, $timeout, leadAndOpportunityService) {
$scope.selectDataSource = function (condition) {
var dataSource = [];
var dataSource = $scope.leadsDataSource.filter(function (item) {
return item.typeName === condition;
});
$scope.leadsDataSource = [];
$scope.leadsDataSource = dataSource;
console.log($scope.leadsDataSource);
}
}]);
HTML
<ng-senab-grid datasource="{{ leadsDataSource }}" defaultdata="{{defaultColumns}}" skipdata="{{ skipColumns }}" enablepersonalisation="true"></ng-senab-grid>
Directive
angular.module('page.gridView.drct', []).directive("ngSenabGrid", ["$rootScope", function ($rootScope) {
return {
restrict: "E",
templateUrl: "pages/gridView/page.gridView.tpl.html",
scope: {
enablePersonalisation: "#enablepersonalisation",
datasource: "#datasource",
defaultdata: "#defaultdata",
skipdata: "#skipdata"
},
}
}]
);
Controller B
var _datasource = JSON.parse($scope.datasource);
//rest the data follows
So when $scope.leadsDataSource gets changes on Controller A, then the
var _datasource = JSON.parse($scope.datasource);
also should get changed
I dont know if it is possible or not. But I need to change the data
Thanks in advance
remove the curly brackets of the variable.since this is a directive no need to add curly brackets
<ng-senab-grid datasource="leadsDataSource" defaultdata="defaultColumns" skipdata="skipColumns" enablepersonalisation="true"></ng-senab-grid>
if u want to get the value of the variable then use "=" if u use "&" it will only get the string
scope: {
enablePersonalisation: "=enablepersonalisation",
datasource: "=datasource",
defaultdata: "=defaultdata",
skipdata: "=skipdata"
},
also inject the directive module to ur angular module
angular.module('page.leadAndOpportunity.ctrl', ['page.gridView.drct'])
A simple explanation to keep in mind about different types of scopes would be below.
# Attribute string binding (String)
= Two-way model binding (model)
& Callback method binding (method)
According this you should be using Two-way binding instead of Attribute string binding because The model in parent scope is linked to the model in the directive's isolated scope. Changes to one model affects the other, and vice versa.
I would prefer using bindToController property definition in the directive. When set to true in a directive with isolated scope that uses controllerAs, the component’s properties are bound to the controller rather than to the scope.
That means, Angular makes sure that, when the controller is instantiated, the initial values of the isolated scope bindings are available on this, and future changes are also automatically available.
Check the Below sample fiddle example for more understanding
var myApp = angular.module('myApp', []);
myApp.controller('MyController', function($scope) {
$scope.change = function() {
$scope.fullname = 'Keshan';
}
$scope.reset = function() {
$scope.fullname = 'Fill Your Name';
}
});
myApp.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
name: '='
},
controller: function($scope) {
this.name = 'Fill Your Name';
},
controllerAs: 'ctrl',
bindToController: true,
template: '{{ctrl.name}}',
};
});
<script src="https://code.angularjs.org/1.3.7/angular.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<button ng-click="change()">Change</button>
<button ng-click="reset()">Reset</button>
<my-directive name="fullname"></my-directive>
</div>
Further Reading

Directive with transclude, data binding not working in templateUrl

I have a directive with transclude:true. However, the data binding works when i use a template:"" but not when i use templateUrl:""
Below you can find my directive. The rsCarousel.html template contains the same code as the template"" property.
When using the template property i get the vm.carouselId on screen but not when using the templateUrl property.
Why is this?
Thx,
(function () {
'use strict';
angular.module('skynetDashboard').directive('rsCarouseli', carouseli);
function carouseli(){
var directive = {
restrict:"EA",
scope:{
carouselData:"=",
carouselId:"#",
carouselOptions:"#"
},
transclude:true,
templateUrl:"js/directive/rsCarousel.html",
//template:"<strong>ID: {{vm.carouselId}}</strong><ul ng-transclude></ul>",
bindToController:true,
controllerAs:"vm",
link:link,
controller:controller
}
return directive
function link(scope){
console.log(scope.vm)
}
controller.$inject = [""]
function controller(){
}
}
})();
That is strange, scope binding should work in both cases.
Be sure that your template file is not cached by the browser (and using an old version of it).

Highlights don't work in external template

I am a noob with angularjs and I have a problem.
I am using prism.js or highlights.js in my web (same result). It works correctly into index.html but It doesn't work in other templates that I load with ngRoute.
I believe that the problem is angularjs only it renders one more time the html and it doesn't work when I load my content-principal.html.
INDEX.HTML
//<pre><code class="language-javascript">
colour syntax is ok
//</code></pre>
APP.JS
ionicEsApp.config(function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'templates/content-principal.html',
//controller: 'IonicEsController'
}).
content-principal.html
//<pre><code class="language-javascript">
colour syntax is NO work
//</code></pre>
¿any solution? Thanks and sorry by my english :P.
SOLVED.
We need:
index.html
prims.js and prism.css from http://prismjs.com/#basic-usage
app.js
To create a new directive (VERY IMPORTANT before from .conf)
var ionicEsApp = angular.module('ionicEsApp', [
'ngRoute',
'ngResource',
'ionicEsController'
]);
ionicEsApp.directive('ngPrism', [function() {
return {
restrict: 'A',
link: function($scope, element, attrs) {
element.ready(function() {
Prism.highlightElement(element[0]);
});
}
}
}]);
ionicEsApp.config(function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'templates/content-principal.html',
//controller: 'IonicEsController'
}).
otherwise({
redirectTo: '/'
});
});
content-principal.html
We have to use the new directive into code tag.
<pre><code ng-prism class="language-javascript">
alert("Prims is ok");
</code></pre>
NOTE: There is a problem with html, we need replace the < symbol by &lt. Example:
<pre><code class="language-markup">
&lth1> Hello! &lt/h1>
</code></pre>
Can't comment on an answer yet, but I found this technique useful.
If you load your templates 'manually' and insert the text into the dom, Angular will automagically convert the content to HTML entities, meaning your raw template are still readable, but display correctly.
In my application I use $sce and $templateRequest to get the template, then set an angular template variable to the value of the fetched template.
A couple of notes:
I have multiple code samples per directive instance, identified by a codeType variable
My templates filenames are in the form of _{codeType}.sample e.g. _css.sample
The template location is passed in as an attribute of the directive in the dom
The dom element containers are identified by class .sample-{codeType} e.g .sample-css
The angular placeholder is identified by {{sample{codeType}} e.g. {{samplecss}}
To prevent race conditions, I use $timeout to wait a beat and allow the current $apply() to complete before calling Prism on the code.
This method also allows for multiple types of code with similar outputs - for example, in my styleguide I show both the output HTML (codeType = 'html') and the un-rendered Angular templates (codeType = 'ng') - both require the Prism .language-markup class.
This can be simplified a lot if you only have one code sample per directive.
function StyleGuideComponentController($scope, $element, $templateRequest, $sce, $timeout)
{
var codeSampleTypes =
[
'html',
'ng',
'ngjs',
'css',
'less'
];
insertAllCodeSamples();
function insertAllCodeSamples()
{
var key;
for (key in codeSampleTypes)
{
insertCodeSample(codeSampleTypes[key]);
}
}
function insertCodeSample(codeType)
{
var sampleUrl = $scope.templateLocation + '/_' + codeType + '.sample',
sampleCode = $sce.getTrustedResourceUrl(sampleUrl);
$templateRequest(sampleCode).then(function(template)
{
var codeElement = $element.find('.sample-' + codeType)[0],
prismLanguage = codeType,
prismLanguageTypes =
{
'html' : 'markup',
'ng' : 'markup',
'js' : 'javascript',
'ngjs' : 'javascript'
},
key;
for (key in prismLanguageTypes)
{
if (prismLanguage === key)
{
prismLanguage = prismLanguageTypes[key];
}
}
codeElement.className += ' language-' + prismLanguage;
$scope['sample' + codeType] = template;
$timeout(function()
{
Prism.highlightElement(codeElement);
});
}, function()
{
$scope['sample' + codeType] = 'An error occurred' +
' while fetching the code sample';
});
}
return {
restrict : 'E',
scope :
{
templateLocation: '='
},
controller : StyleGuideComponentController
};
}
There is a really easy way to do this if you are using ng-view to load the template:
if you have something like this:
<div ng-controller="MainCtrl">
<div id="content" ng-view>
</div>
</div>
You can add this to your MainCtrl controller:
$scope.$on('$viewContentLoaded', function(){
Prism.highlightAll();
});
Now, if you use the default way to highlight the code:
<pre><code class="language-javascript">
Prism will highlight it
</code></pre>
Prism will highlight it!

AngularJS : broadcast event from directive

I've seen people doing this from wherever in their code:
$rootScope.$broadcast('someEvent', someParameter);
and then in some controller:
$rootScope.$on('someEvent', function(event, e){ /* implementation here */ });
Now, I'd like to broacast an event from a directive. Is it good practice to broadcast it at rootScope level ? I would like to handle this event in a controller. Can I use $scope, or do I still have to listen on $rootScope ?
In my case, I just want to broadcast an event from a directive to the controller of the view, in which I use the directive. Does it still make sense to use broadcast then?
I would have the directive call a method on the controller, which is specified in the HTML where the directive is used:
For a directive that uses an isolate scope:
<div my-dir ctrl-fn="someCtrlFn(arg1)"></div>
app.directive('myDir', function() {
return {
scope: { ctrlFn: '&' },
link: function(scope, element, attrs) {
...
scope.ctrlFn({arg1: someValue});
}
For a directive that does not use an isolate scope:
<div my-dir ctrl-fn="someCtrlFn(arg1)"></div>
app.directive('myDir', function($parse) {
return {
scope: true, // or no new scope -- i.e., remove this line
link: function(scope, element, attrs) {
var invoker = $parse(attrs.ctrlFn);
...
invoker(scope, {arg1: someValue} );
}
It's usually a good idea not to use the $rootScope as it's global and you shouldn't pollute it unless you really know what you're doing. I would recommend that you read this article about communication between services, directives and controllers.
Here is a TypeScript example of how to call back a method on the controller from an embedded directive. The most important thing to note is that the directive's parameter name for your callback uses a & when defined, and when calling that callback you should not use positional parameters but instead use an object with properties having the names of the parameters in the target.
Register the directive when you create your app module:
module MyApp {
var app: angular.IModule = angular.module("MyApp");
MyApp.Directives.FileUploader.register(app);
}
The registration code is as follows:
module MyApp.Directives.FileUploader {
class FileUploaderDirective implements angular.IDirective {
public restrict: string = "E";
public templateUrl: string = "/app/Directives/FileUploader/FileUploaderDirective.html";
//IMPORTANT - Use & to identify this as a method reference
public scope: any = {
onFileItemClicked: "&"
};
public controller: string = "MyApp.Directives.FileUploader.Controller";
public controllerAs: string = "controller";
public bindToController: boolean = true;
public transclude: boolean = true;
public replace: boolean = true;
}
export function register(app: angular.IModule) {
app.controller("MyApp.Directives.FileUploader.Controller", Controller);
app.directive("fileUploader", () => new FileUploaderDirective());
}
}
The directive's controller would look like this
module MyApp.Directives.FileUploader {
export class Controller {
public files: string[] = ["One", "Two", "Three"];
//The callback specified in the view that created this directive instance
public onFileItemClicked: (fileItem) => void;
// This is the controller method called from its HTML's ng-click
public fileItemClicked(fileItem) {
//IMPORTANT: Don't use comma separated parameters,
//instead use an object with property names to act as named parameters
this.onFileItemClicked({
fileItem: fileItem
});
}
}
}
The directive's HTML would look something like this
<ul>
<li ng-repeat="item in controller.files" ng-click="controller.fileItemClicked (item)">
{{ item }}
</li>
</ul>
The main view will have an instance of your directive like so
<body ng-app="MyApp" ng-controller="MainController as controller">
<file-uploader on-file-item-clicked="controller.fileItemClicked(fileItem)"/>
</body>
Now all you need on your MainController is a method
public fileItemClicked(fileItem) {
alert("Clicked " + fileItem);
}

How can I easily implement multiple layouts in Angular JS?

I need to be able to specify different layouts for different routes, and most preferably I would like to be able to define layouts and other parameters in an object in route config and have them propagate on route change.
Here's the way I solved it in my current project.
Working demo to be found here
What if you could define objects in your $routeProvider.when(...) block like this:
Route definition:
$routeProvider
.when('/', {
templateUrl: 'main.html',
controller: 'MainCtrl',
resolve: {
interceptor: interceptWith('routeChangeInterceptor', {
stateClass: {
fullWidthLayout: true
}
})
}
});
And have them propagate and use it to add classes with an ng-class like interface using the stateClass objects like this?
HTML:
<body state-class="{'full-width-layout': fullWidthLayout}"> ... </body>
<div class="some-class" state-class="{'some-class': someValue}"> ... </div>
How to:
This is using an interceptWith(...) helper that simply injects a service and calls it with given parameters, but it could also be implemented using array notation like this
interceptor: ['serviceToInject', function(injectedService) { .. }];
Only this way It's DRYer. See demo fore more on this.
The service that is used to broadcast the object from the route definition:
//This interceptor is responsible for emiting an event on rootScope
.factory('routeChangeInterceptor', ['$rootScope', function($rootScope) {
var _prepare = function(state) {
$rootScope.$broadcast('data:broadcast-state', state);
};
return {
prepare: _prepare
};
}]);
The directive used to add/remove classes based on the broadcasted state event object looks like this:
//This directive receives and $parses object/classname mappings,
//and it will add or remove the defined class for every mapping that is defined.
angular.module('broadcastState')
.directive('stateClass', ['$parse', function ($parse) {
var _linkFn = function link(scope, element, attrs) {
scope.$on('data:broadcast-state', function(e, state) {
//Set defaults
state = state || {};
state.stateClass = state.stateClass || {};
var classes = $parse(attrs.stateClass)(state.stateClass);
angular.forEach(classes,function(value,className) {
if(value && typeof value === 'boolean')
{
element.addClass(className);
}
else
{
element.removeClass(className);
}
});
});
}
return {
restrict: 'A',
link: _linkFn
};
}]);
Check out the plnkr to read more.
Looks like https://github.com/angular-ui/ui-router from the Angular team is the best approach.
Try this http://angular-route-segment.com/ (A lightweight extension for AngularJS $route service which supports tree-like nested views and routes, and advanced flow handling)

Resources