Dependency injection in Angular Components like directives - javascript

Here's one thing I'm used to do with angular directives
angular.module('app.directives').directive('login', ['$templateCache', function ($templateCache) {
return {
restrict: 'E',
template: $templateCache.get('directives/login/login.html'),
controller: 'LoginController as vm',
scope: true
};
}]);
I've grown very attached to using Template Cache to inject HTML content in my directive's template. Now with Angular 1.5 there's this new thing all the cool kids are using called component() which I'm giving a look to see if it's really good and I'm stuck at this very beginning part: how to inject things in the component itself (not in the controller)?
In this case you can see that I'm injecting into the login directive the $templateCache dependency. How would I rewrite this directive as a component? (keeping in mind my desire to use $templateCache over templateUrl)

Well, In Angular 1.5 components, template property can be a function and this function is injectable (documentation).
So, you can just use something like:
...
template: ['$templateCache', function ($templateCache) {
return $templateCache.get('directives/login/login.html')
}]
...
Few links from google search: one and two.
Hope it will help.

MaKCblMKo 's answer is right, but remember that AngularJS will check the templateCache first before going out to to retrieve the template. Therefore, you don't have to make this call in your directive or component.
angular.module('myApp', [])
.component('myComponent',{
templateUrl: 'yourGulpPattern'
})
.run(function($templateCache) {
$templateCache.put('yourGulpPattern', 'This is the content of the template');
});
https://jsfiddle.net/osbnoebe/6/

Related

custom $compile directive applies only sometimes in same scope

thanks for looking. I'll dive right in:
A JSON object has HTML links with ng-click attributes, employing ng-bind-html, with $sce's trustAsHtml to make the HTML safe. I have also used a custom angular-compile directive to compile the angular click listener into the app after the json is loaded in a $q promise.
All of this works as intended, at first glance...
JSON
{
"text" : "Sample of text with <a data-ng-click=\"__services._animation.openModal('g');\">modal trigger</a>?"
}
VIEW
<p data-ng-bind-html="__services.trustAsHTML(__data.steps[step.text])"
data-angular-compile></p>
DIRECTIVE
angular.module('app.directives.AngularCompile', [], ["$compileProvider", function($compileProvider) {
$compileProvider.directive('angularCompile', ["$compile", function($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.angularCompile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
}])
}]);
The Issue:
Okay, so it works. My app loads, it serves the safe HTML, and my ng-click opens the modal, passing it's params. I see class='ng-binding' on the surrounding p tag, class="ng-scope" on the a tag in the generated html. Hooray!
The next order of business is to write that data in a another model that tracks progress, and run it through the same ng-bind, trustAsHTML, angular-compile treatment in another view. Just copying the data into a sibling object.
Here's where it fails!
<p data-ng-bind-html="__services.trustAsHTML(__state.modal.text)"
data-angular-compile></p>
In the second view, which is a modal in the same scope ($rootScope) on the same page - the bind, and trustAsHTML Angular is applied. But the link is not clickable, and no class="ng-scope" is generated on the a tag.
If fFurther explanation of my set-up might help understand the issue, let me detail it here. All initial app is set up by the concierge and it store most data in $rootScope:
return angular
.module('app', [
'ngResource',
'ngSanitize',
'ui.router',
'oslerApp.controllers.GuideController',
'oslerApp.services.ConciergeService',
'oslerApp.services.DataService',
'oslerApp.services.LocationService',
'oslerApp.services.StatesService',
'oslerApp.directives.AngularCompile',
])
.config(function($stateProvider, $urlRouterProvider) {
// For any unmatched url, redirect to landing
$urlRouterProvider.otherwise("/");
// Now set up the states
$stateProvider
.state('guide', {
url: "/guide",
templateUrl: "views/guide.html",
controller: 'GuideController as guide'
})
.state('contact', {
url: "/contact",
templateUrl: "views/contact.html"
})
})
.run(function(ConciergeService) {
ConciergeService.init();
});
I've spent 2 days refactoring my whole site to see if it was because the modal was in it's own directive, but putting it within the same template and scope didn't seem to help me here.
Lesson: If you have to refactor everything and still make no strides, you're doing it wrong.
To solve this, 2 days of hair pulling later, I made a tiny little service and pass the problem text through that:
compiler: function(element, template, content, scope) {
element = $(element);
template = template[0] + content + template[1];
var linkFn = $compile(template);
content = linkFn(scope);
element.replaceWith(content);
},

Angular directive inside ng-template with modal

I am curious about the way angular works with preloading directives since I have a problem with a directive that resides in a <script> tag and ng-template.
When I pause execution in chrome dev-tools, during the initial document load, I can clearly see that the code inside the directive's controller does not get called if my directive lies in some arbitrary template. Here is the example code, when e.g. myDirective is included in index.html as a part of myModule.js module, also included both in index and in the main app module:
This is some other directive's html containing the problematic myDirective
<script type="text/ng-template" id="callThis">
<myDirective></myDirective>
</script>`
and I call it on click with ngDialog like this
ngDialog.open({
template: 'callThis',
scope: $scope
});
and it can't run the directive since it doesn't have any html to work with (thats the error, about some html element missing).
Finally here is the code for the module which holds myDirective
angular.module('myModule', ['myDirectiveTemplates', 'myDirectiveDirective'])
angular.module('myDirectiveTemplates', []).run(["$templateCache", function($templateCache) {$templateCache.put("myDirectiveTemplate.html","<h1></h1>");}]);
angular.module('myDirectiveDirective', []).directive('myDirective', function ($window, $templateCache) {
return {
restrict: 'E',
template: $templateCache.get('myDirectiveTemplate.html'),
controller: function($scope) {
//some arbitrary code
}
};
})
Interestingly if i put <my-directive></my-directive> right in index.html file it works ok, and the code inside the controller gets loaded on startup. I'm unsure how to solve this.
From what I understand of this problem you need to use the $compile service. There is a tutorial here that might help : $compile
The reason given in the tutorial is:
"The newly minted template has not been endued with AngularJS powers
yet. This is where we use the $compile service..."
And quoted from the Angular Docs:
Compiles a piece of HTML string or DOM into a template and produces a
template function, which can then be used to link scope and the
template together.
Here is a brief code example as per the tutorial :
app.directive('contentItem', function ($compile) {
/* EDITED FOR BREVITY */
var linker = function(scope, element, attrs) {
scope.rootDirectory = 'images/';
element.html(getTemplate(scope.content.content_type)).show();
$compile(element.contents())(scope);
}
/* EDITED FOR BREVITY */
});

Working Angularjs template cache on build and deployment

I am developing angular directives that used html template.
angular.module("app")
.directive("ticket", [function(){
return {
restrict: "E",
templateUrl: "app/ticket/ticket.html"
}
}])
This directive is working and I can change ticket.html content and run application to see changes.
But I read about $templateCache method to increase performance of big projects. And I can use grunt-angular-template to create all of template cache.
But I need to change my directive.
angular.module("app")
.directive("ticket", ["$templateCache",function($templateCache){
return {
restrict: "E",
template: $templateCache.get("app/ticket/ticket.html")
}
}])
Can I separate the build and deployment usage of template.
templateUrl: "app/ticket/ticket.html"
template: $templateCache.get("app/ticket/ticket.html")
I will select one of that ways. What is the professional approach for this?
You do not need to update this, you will automatically get it from the cache if you leave your code as it is, here you are just passing a template path to angular and angular gets it for you from the cache. $templateCache.get("app/ticket/ticket.html") is only needed if you want to load a template via js.

angularjs routing behaviour of content and function calls

I'm new to Angular.js which is why I have a basic question regarding routing. I figured out how to create routes and inject specific .htmls by $routeProvider
var app = angular.module('test', ['ngRoute']);
app.config(function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'routes/view2.html'
});
});
but what I really don't get is how content or function of view2.html are handled in Angular.
Lets take view2.html. It has a <p> with some text in a specific color. Nothing to special. But also it has a little slideshow which is called by $('slideshow').cycle() function.
All what happens is it displays me the <p> tag in a different color and no slideshow function is called on my rootsite of the app.
Could you give me some approach how to actually solve this?
Thanks
Just load required view and then compile it. During compilation Angular processes all directives in view.
If you want to do this proper(Angular) way you should put such code like $('slideshow').cycle() inside of directive. And then use it like
<div my-slideshow=""></div>
angular.module('myModule', [])
.directive('mySlideshow', [function () {
return {
restrict: 'A',
link: function (scope, element) {
element.cycle();
}
}
}]);
Directives documentation
Much more comprehensive documentation

AngularJS directive not showing up on template

I've got a tiny problem with an angular directive that's now working and I don't know why. I think it's a fairly simple issue that I'm overlooking, maybe you can help me out.
Directive is defined like this:
angular.module('directives', [])
.directive('my-directive', function () {
return {
restrict: 'AE',
scope: {
name: '=name'
},
template: '<h1>{{name}}</h1>'
};
});
Then index.cshtml:
<my-directive name="test"></my-directive>
Application.js:
var app = angular.module('MyApp', [
...,
'directives'
]);
And here's controllers.js
angular.module('controllers', ['apiServices', 'directives'])
.controller('homecontroller', function($scope, $resource, webApiService, $log, $translate, $localStorage, $sessionStorage) {
Ok confirmed that directives.js is loaded, otherwise application.js nags about 'unknown module'. There are no error messages in the console, the thing just doesn't show. Any ideas?
EDIT
So as pointed out, I changed the directive name to camelCase, but still no luck:
<my-directive name="John Doe"></my-directive>
And
.directive('myDirective', function () {
But nothing is showing yet.
EDIT
Problem is that angular expects an object to be passed into the attribute, not a string literal. If you create an object person = { name: 'John' }, pass the person in, then write {{ person.name }} ( assuming we named the attribute person + scope var person too ).
During normalization, Angular converts - delimited name to camelCase.
So use camelCase while specifying the directive inside JS:
.directive('myDirective', function () {
Fiddle
I'm sure you've figured this out already, but if you change your scope definition for name to be
scope: {
name: '#'
}
you will then be able to pass a string. The '#' interpolates the attribute while the '=' binds it. Additionally, you don't need to include an attribute name if it is the same as the scope variable.
The problem appears to be in the directive definition. You note in your question that Angular expects an object; this is true for the "=" scope, but not for the "#" scope. In the "#" scope, Angular expects a string only. I have created a snippet below.
Too many modules
Unless you are reusing the directive in multiple applications, do not create a new module for it. Add the directive definition to the module that you created for the application. In my example below, I called the module back by using "angular.module( moduleName )"... When only one argument is used, Angular returns the existing object rather than creating a new one. This is how we can separate the code into many files.
Things to Note
You will notice the following:
You do not need to load the module into the app variable. Calling the Singleton each time is actually safer and easier on memory management.
The directive is in camel case, as you have already noted.
I am setting the name attribute to a string value and not an object; this works because of the "#" scope setting.
The div is set to ng-app='MyApp'. This usually is set to the html element, but I did not want to mess with the DOM on Stack Exchange. The ng-app directive can be set on any element, and the directives associated with that module will be applied on all elements that are within that element's scope. Without the ng-app directive, Angular does not know which module to run on the page.
//app.js - this defines the module, it uses two parameters to tell the injector what to do.
angular.module('MyApp',[]);
//directive.js stored elsewhere
//this calls back the module that has been created. It uses one parameter because the injector is no longer needed.
angular.module('MyApp').directive('myDirective', function () {
return {
restrict: 'AE',
scope: {
name: '#'
},
template: '<h1>{{name}}</h1>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp">
<h1>Successful Load</h1>
<my-directive name="test"></my-directive>
<p>By applying the directive definition to the MyApp module, the MyApp module knows to activate the directive within this scope. In this form, it does not get injected.</p>
</div>
Using Injection
When you have a different module for each and every directive or controller, each one must be injected into the application's module definition. This leaves a lot of room for error. As a best practice, only create a new module when necessary, and make the module a container for a group of related functionality and not a single item.
The code below demonstrates proper injection.
angular.module( "MyApp", ['ReusableDirectives'] );
angular.module( "MyApp" ).directive( "myDirective", function(){
return {
restrict: "AE",
scope: { name: "#" },
template: "<p>This is the directive I defined in the example above. It uses <u>the same module</u> as my main application, because it is not going to be reused over and over again. In fact, I will need it just for this application, so I don't need to complicate things with a new module. This directive takes an attribute called 'name' and if it is a string allows me to manipulate the string within my templates scope to do things like this: {{'hello ' + name + '!'}}</p>"
};
} );
angular.module( "ReusableDirectives", [] );
angular.module( "ReusableDirectives" ).directive("reusableDirective", function(){
return {
restrict: "E",
template: "<p>This is a directive that I intend to use in many, many applications. Because I will reuse it so much, I am putting it in a separate module from my main application, and I will inject this directive. This is the only reason that this directive is not in the same module as the one I defined above.</p>"
};
} ).directive("reusableDirective2", function(){
return {
restrict: "E",
template: "<p>This is a second directive that I intend to use in multiple applications. I have stored it in a module with the first directive so that I can freely inject it into as many apps as I like.</p>"
};
} )
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApp">
<h1>Successful Load</h1>
<my-directive name="Johnny"></my-directive>
<p>By applying the directive definition to the MyApp module, the MyApp module knows to activate the directive within this scope. In this form, it does not get injected.</p>
<h3>Injected Directives</h3>
<reusable-directive></reusable-directive>
<reusable-directive2></reusable-directive2>
</div>
Keep it simple. Define your directives on a single module for your application. Once you have that done and working, if you need the directives again in another application, refactor and experiment with injections at that time after you have some more Angular practice under your belt.
You have a bright future with Angular, keep up the good work!
Your directive must be camel-cased
.directive('myDirective', function () {
then in your html, your are free whether to call it my-directive or myDirective
Both are valid
<my-directive name="test"></my-directive>
<myDirective name="test"></myDirective>
Just to follow up on this, I had to use the following way to get my directive to work.
<my-directive name="test"></my-directive>

Categories

Resources