Creating a plugin system in Angular JS with the $compile service

Angular JS directives are powerful. Using them allows you to manipulate pretty much everything in the DOM that you would want to. But there is one exception. Dynamically creating a directive depending on data received from the server, something often used for plugin systems. Luckily we can access the $compileProvider directly to work around these limitations. Plugins Say you are designing a plugin system. Each plugin is implemented as a different directive. »

Studying the Angular JS Injector - loading modules

(This post is part of a series studying the AngularJS injector) A module gets loaded with the following code: function loadModules(modulesToLoad){ var runBlocks = [], moduleFn, invokeQueue, i, ii; forEach(modulesToLoad, function(module) { if (loadedModules.get(module)) return; loadedModules.put(module, true); try { if (isString(module)) { moduleFn = angularModule(module); runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { var invokeArgs = invokeQueue[i], provider = providerInjector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } } else if (isFunction(module)) { runBlocks.push(providerInjector.invoke(module)); } else if (isArray(module)) { runBlocks.push(providerInjector.invoke(module)); } else { assertArgFn(module, 'module'); } } catch (e) { if (isArray(module)) { module = module[module.length - 1]; } if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { // Safari & FF's stack traces don't contain error.message content // unlike those of Chrome and IE // So if stack doesn't contain message, we create a new string that contains both. »

Studying the Angular JS Injector - the twin injectors

(This post is part of a series studying the AngularJS injector) When Angular creates the injector, it actually creates two injectors: providerInjector = (providerCache.$injector = createInternalInjector(providerCache, function() { throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); })), instanceInjector = (instanceCache.$injector = createInternalInjector(instanceCache, function(servicename) { var provider = providerInjector.get(servicename + providerSuffix); return instanceInjector.invoke(provider.$get, provider); })); Two parameters are passed to the createInternalInjector function. The first is the cache to use to look up instances (a simple object). »

Studying the Angular JS Injector - getService

(This post is part of a series studying the AngularJS injector) The getService function is the work-horse of invoke. This is the method that takes a service name and attempts to locate it in the list of registered services. function getService(serviceName) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); } return cache[serviceName]; } else { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; return cache[serviceName] = factory(serviceName); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; } throw err; } finally { path.shift(); } } } When the injector is created it is passed two parameters, cache and factory. »

Studying the Angular JS Injector - instantiate

(This post is part of a series studying the AngularJS injector) Whilst invoke calls a function with it’s parameters injected, instantiate will contruct a new object with it’s constructor parameters injected. instantiate gives us an excellent insight into how javascript objects work. In javascript, a class is just a function and an class instance is just a function that has been invoked with the new operator. Say we have a simple class : function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } We can add methods to this class via the functions prototype property. »

Studying the Angular JS Injector - invoke

(This post is part of a series studying the AngularJS injector) The invoke method invokes the given function with the parameters injected. function invoke(fn, self, locals){ var args = [], $inject = annotate(fn), length, i, key; for(i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; if (typeof key !== 'string') { throw $injectorMinErr('itkn', 'Incorrect injection token! Expected service name as string, got {0}', key); } args.push( locals && locals.hasOwnProperty(key) ? »

Studying the Angular JS Injector - annotate

(This post is part of a series studying the AngularJS injector) In order for the Injector to know what to inject into a given functions parameters, it needs a list of these parameters. This is what the annotate function does. There are three different ways in Angular to annotate your methods. Use an array. The last element of the array is the function, the rest is a list of the parameter names. »

Studying the Angular JS Injector - intro

I am truly impressed with the elegance of AngularJS and have been studying the source code to fully understand it. In this series I will be studying the Injector module as this is one of the core modules around which the rest of the framework revolves. In src/auto/injector.js there is a method createInjector. It is this method that kicks off the whole process. After a bit of setup this method returns an instanceInjector object. »