Git Product home page Git Product logo

angular-aop's Introduction

Travis CI Join the chat at https://gitter.im/mgechev/angular-aop

Online demo

If you prefer learning by doing (trial and error), come right this way.

API

AngularAOP allows you to apply aspect-oriented programming in your AngularJS applications.

If you notice that you have cross-cutting concerns you can isolate them in individual services and apply them as follows:

myModule.config(function ($provide, executeProvider) {
  executeProvider.annotate($provide, {
    ServiceToWove: [{
      jointPoint: JOINT_POINT,
      advice: ADVICE_NAME,
      methodPattern: /Special/
      argsPattern: [/arg1/, /arg2/]
    }, {
      jointPoint: JOINT_POINT
      advice: ADVICE_NAME
    }]
  });
});

The joint-points supported in the current version of the framework are:

  • after - the advice will be invoked after the target method
  • afterResolveOf - the advice will be invoked after the promise returned by target method has been resolved and after the resolve callback attached to the promise is invoked
  • aroundAsync - the advice will be invoked before the target method was invoked and after the promise returned by it was resolved
  • around - the advice will be invoked before the target method was invoked and after it was invoked
  • beforeAsync - the target method will be called after the promise returned by the advice was resolved
  • before - the target method will be invoked after the advice
  • onRejectOf - the advice will be invoked when the promise returned by the target method was rejected
  • onResolveOf - the advice will be invoked after the promise returned by the target method was resolved but before the resolve callback attached to the promise is invoked
  • onThrowOf - the advice will be called if the target method throws an error

For additional information about Aspect-Oriented Programming and AngularAOP visit the project's documentation and this blog post.

Change log

v0.1.0

New way of annotating. Now you can annotate in your config callback:

DemoApp.config(function ($provide, executeProvider) {
  executeProvider.annotate($provide, {
    ArticlesCollection: {
      jointPoint: 'before',
      advice: 'Logger',
      methodPattern: /Special/,
      argsPatterns: [/arg1/, /arg2/, ..., /argn/]
    }
  });
});

v0.1.1

Multiple aspects can be applied to single service through the new way of annotation:

DemoApp.config(function ($provide, executeProvider) {
  executeProvider.annotate($provide, {
    ArticlesCollection: [{
      jointPoint: 'before',
      advice: 'Logger',
    }, {
      //aspect 2
    }, {
      //aspect 3
    }, ... , {
      //aspect n
    }]
  });
});

Note: In this way you won't couple your target methods/objects with the aspect at all but your target service must be defined as provider.

v0.2.0

Added forceObject property to the rules. This way issues like #12 will not be reproducable since we can force the framework to wrap the target's method, insted of the target itself (in case the target is a function with "static" methods").

Issues fixed:

  • Once a function is wrapped into an aspect its methods are preserved. We add the target to be prototype of the wrapper, this way using the prototype chain the required methods could be found.

v0.2.1

Added tests for:

  • Before async joint-point
  • On resolve joint-point

Add JSCS and update Gruntfile.js

v0.3.1

  • deep config property, which allows adding wrappers to prototype methods
  • Fix forceObject

v0.3.1

  • Wrap the non-minified code in build in IIFE (Issue 15)
  • Single 'use strict'; at the top of the IIFE

v0.4.0

  • Add the joint-point names as constants to the executeProvider, so now the following code is valid:
myModule.config(function ($provide, executeProvider) {
  executeProvider.annotate($provide, {
    ServiceToWove: [{
      jointPoint: executeProvider.ON_THROW,
      advice: ADVICE_NAME,
      methodPattern: /Special/
      argsPattern: [/arg1/, /arg2/]
    }, {
      jointPoint: executeProvider.BEFORE,
      advice: ADVICE_NAME
    }]
  });
});
  • Add more tests

v0.4.1

  • Special polyfill for IE9 of Object.setPrototypeOf.

Roadmap

  1. joinpoint.proceed()
  2. Use proper execution context inside the target services. This will fix the issue of invoking non-woven internal methods.
  3. More flexible way of defining pointcuts (patching $provide.provider might be required)

Known issues

Circular dependency

This is not issue in AngularAOP but something which should be considered when using Dependency Injection.

Note that if the $injector tries to get a service that depends on itself, either directly or indirectly you will get error "Circular dependency". To fix this, construct your dependency chain such that there are no circular dependencies. Check the following article, it can give you a basic idea how to procceed.

Contributors

mgechev david-gang Wizek slobo christianacca peernohell
mgechev david-gang Wizek slobo christianacca peernohell

License

AngularAOP is distributed under the terms of the MIT license.

angular-aop's People

Contributors

bitdeli-chef avatar christianacca avatar david-gang avatar gitter-badger avatar mgechev avatar peernohell avatar slobo avatar wizek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

angular-aop's Issues

AngularAOP for advising controller

Hi there,
Currently, I see that AngularAOP on has the support for advising Angular's service. However, it might be useful if it can support advising controllers as well, because you can have Authorization, Exception handling in controllers, not only in services.

Would be very nice to have that,
Cheers

Name of the original function cannot be determined when function called without a context

It's possible that a public method of an angular service will be called as a "standalone" function without a function receiver being defined - eg: someFunction.call(null)

When this happens and the original method was created as an anonymous function then the name of the function cannot be determined.

I have a solution to this: remember the name of the service property to which the function was originally bound. I'm going to send a pull request for this...

Enhancement: add pointcut onNotify

I wish to use AngularAOP in a project I'm working on, the use case I'm thinking of would really benefit from being able to define pointcuts for the notify callback. Are there any plans of introducing this? If you are not able to work on it yourself: would you accept a pull request?

Typescript: undefined is not a function

I've been trying to integrate aop for logging, and followed both the README and a post you've made, with no luck.

My typescript:

/// <reference path="/typings/angularjs/angular.d.ts" />

interface Window { TEST : any; }

module MyApp {
  "use strict";

  export function boot(
    $log : ng.ILogService
  ) {
    $log.info("starting");
  }

  angular
    .module("my_app", [
      "uuid4",
      "ngTouch",
      "AngularAOP"
    ])
    .factory("log_aspect", [() => {
      return (data : any) => {
        console.log("==== HELLO ====");
        console.log(data);
      };
    }])
    .factory("DummyService", [() => {
      return {
        foo: () => { console.log("---- foo ----"); },
        bar: (a : any, b : any) => {
          console.log("---- bar ----", a, b);
          return a * b;
        }
      };
    }])
    .controller("BusController", [
      "DummyService",
      (DummyService : any) => {
        DummyService.foo();
        DummyService.bar(2, 3.14);
      }
    ])
    .config( ($provide : any, executeProvider : any) => {
      executeProvider.annotate($provide, {
        DummyService: [{
          jointPoint: "after",
          advice: "log_aspect"
        }]
      });
    })
    .run([
      "$log",
      window.TEST ? () => {} : boot
    ]);
}

and compiled js:

var MyApp;
(function (MyApp) {
    "use strict";
    function boot($log) {
        $log.info("starting");
    }
    MyApp.boot = boot;
    angular.module("my_app", [
        "uuid4",
        "ngTouch",
        "AngularAOP"
    ]).factory("log_aspect", [function () {
        return function (data) {
            console.log("==== HELLO ====");
            console.log(data);
        };
    }]).factory("DummyService", [function () {
        return {
            foo: function () {
                console.log("---- foo ----");
            },
            bar: function (a, b) {
                console.log("---- bar ----", a, b);
                return a * b;
            }
        };
    }]).controller("BusController", [
        "DummyService",
        function (DummyService) {
            DummyService.foo();
            DummyService.bar(2, 3.14);
        }
    ]).config(function ($provide, executeProvider) {
        executeProvider.annotate($provide, {
            DummyService: [{
                jointPoint: "after",
                advice: "log_aspect"
            }]
        });
    }).run([
        "$log",
        window.TEST ? function () {
        } : boot
    ]);
})(MyApp || (MyApp = {}));

with console output:

 TypeError: undefined is not a function
    at Object.AspectBuilder._getFunctionAspect (main.js:11417)
    at Object.AspectBuilder._getObjectAspect (main.js:11434)
    at AspectCollection.AspectBuilder.buildAspect [as after] (main.js:11412)
    at main.js:11392
    at Object.invoke (main.js:3944)
    at Object.origProvider.$get (main.js:3896)
    at Object.invoke (main.js:3944)
    at main.js:3974
    at getService (main.js:3930)
    at Object.invoke (main.js:3942)

If there's any help you can provide it would be greatly appreciated! Really want to integrate this~


2015-02-14: Edited to port full typescript and compiled js

IE9

Hello,

This lib seems not to work with IE9. Is IE9 supported?

ngResource gets incorrectly wrapped into a function aspect

I'm using angular-aop to put an around advice for the ngResource I use to call server-side API. Like this:

myServices.factory('WelcomeService', [ '$resource', 'execute', 'MyAroundAspect', 
      function($resource, execute, ServerCommState ) {
          return execute(MyAroundAspect).around( $resource('/api/welcome/:id', {
              id: '@id'
          }, {
              create: {
                  method: 'POST',
                  params: {
                      nick: '@nick'
                  }   
              },  
              update: {
                  method: 'PUT'
              }
          }));
      }
  ]);

When calling this service's get method, I get an undefined exception, unless I modify angular-aop.js like this:

  //Builds specified aspect
  AspectBuilder = { 
      buildAspect: function (advice, pointcut) {
        var self = this;
        return function (target, rules) {
          // if (typeof target === 'function') {
          //   return self._getFunctionAspect(target, pointcut, advice);
          // } else if (target) {
            return self._getObjectAspect(target, rules || {}, pointcut, advice);
          // }
        };  
      },  

As you may see, I modify the code so buildAspect always return an object aspect. With this modification, the MyAroundAspect gets called, twice per service call.

My solution is not an ideal one, but I'm not using function aspects, so that's fine for me. However, what should be done in order to enable AOP for ngResource without disabling function advices ?

joinpoint.proceed()

Hi,

This issue is for a feature

I would like to have a way to change the args in the join-point. Some AOP lib have something like:

joinpoint.proceed(newArg1, newArg2, ...)

where:

  • When, called, causes the original method to be invoked
  • When called without arguments, the original arguments will be passed.
  • When called with arguments, they will be passed
    instead of the original arguments

I currently am forced to modify the joinpoint.args which is not very clean and also does not allow aspects like memoize as the original method is always invoked.

Cheers,
Jose

resolveArgs is empty in onResolve and AfterResolve

First of all thanks for this awesome package.

My issue is that I noticed that the resolveArgs parameter seems to be always an empty object. I replicated this with the example ArticlesCollection service.

Service:

angular.module('core').factory('ArticlesCollection', function ($q, $timeout) {
  var sampleArticles = [
      { id: 0, title: 'Title 1', content: 'Content 1' },
      { id: 1, title: 'Title 2', content: 'Content 2' },
      { id: 2, title: 'Title 3', content: 'Content 3' }
    ],
    privateArticles = [
      { id: 3, title: 'Title 4', content: 'Content 4' },
      { id: 4, title: 'Title 5', content: 'Content 5' }
    ],
    api = {
      loadArticles: function () {
        var deferred = $q.defer();
        $timeout(function () {
          deferred.resolve(sampleArticles);
        }, 1000);
        return deferred.promise;
      },
      getArticleById: function (id) {
        for (var i = 0; i < sampleArticles.length; i += 1) {
          if (sampleArticles[i].id === id)  {
            return sampleArticles[i];
          }
        }
        return undefined;
      },
      getPrivateArticles: function () {
        return privateArticles;
      }
    };
  return api;
});

Logger:

angular.module('core').factory('Logger', function () {
    return function (args) {
      console.log(angular.toJson(args));
    };
});

Angular-aop config:

angular.module('core').config(function ($provide, executeProvider) {
    executeProvider.annotate($provide, {
        ArticlesCollection: [
            {
                jointPoint: executeProvider.ON_RESOLVE,
                advice: 'Logger'
            },
            {
                jointPoint: executeProvider.AFTER_RESOLVE,
                advice: 'Logger'
            }
          ]
    });
});

Karma test:

(function () {
    ddescribe('ArticlesCollection Service Tests', function () {
        var service;
        // Then we can start by loading the main application module
        beforeEach(module(ApplicationConfiguration.applicationModuleName));

        // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
        // This allows us to inject a service but then attach it to a variable
        // with the same name as the service.
        beforeEach(inject(function ($templateCache, $rootScope, _ArticlesCollection_, $timeout) {
            service = _ArticlesCollection_;
        }));

        it('loadArticles should return articles', inject(function ($timeout) {
          service.loadArticles().then(function(articles) {
            console.log('articles:' + articles);
            expect(articles.length).toEqual(3);
          });
          $timeout.flush();
        }));
    });
}());

Test Results:

Running "karma:unit" (karma) task
INFO [karma]: Karma v0.12.37 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Windows 8 0.0.0)]: Connected on socket vJLU_9iOvsW-83cbU21Z with id 41041485
LOG: '{"when":"onResolve","method":"loadArticles","args":[],"resolveArgs":{}}'
LOG: 'articles:[object Object],[object Object],[object Object]'
LOG: '{"when":"afterResolve","method":"loadArticles","args":[],"resolveArgs":{}}'
PhantomJS 1.9.8 (Windows 8 0.0.0): Executed 1 of 228 SUCCESS (0.121 secs / 0.038 secs)

To me the resolveArgs attribute sounds like it should contain the argument the deferred.resolve() returns, in this case the sampleArticles array. Please correct me if my understanding is wrong. Thanks.

Reduce the number of recursively wrapped aspects

Minko,

One BIG downside of AOP is it makes it harder to debug methods that have aspects applied. One pain point is that when inspecting objects in say chrome developer tools you no longer can hover over a method on an object and see a preview of the method.

Now you have to drill down through multiple levels of wrappers to see this preview as per the attached screen shot
image

One way to reduce this pain is to reduce this nesting. Instead of creating a wrapper for each aspect that is applied to a method, I think a better alternative would be to maintain a single wrapper that collects all the aspects that have been applied to the method. This single wrapper would maintain a stack for each pointcut.

When inspecting an object in the locals window of chrome you would be able to expand each stack to see the list of aspects applied.

Without the above, I think code decorated using the angular-aop library will be too much of a pain to work with once aspects start to pile up on top of one another.

What do you think?

Christian

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.