Giter VIP home page Giter VIP logo

angular-widget's Introduction

Angular Widget Build Status Coverage Status

Lazy load isolated micro-apps in Angular

Demo: http://shahata.github.io/angular-widget/

Slides: https://slides.com/shahartalmi/angular-widget/

Talk (English - Bad quality): https://youtu.be/D8fOHIwz8mY

Talk (Hebrew): http://youtu.be/Wgn2Vid8zCA

What it does

One of the main problems people discover in angular when they try to write a very big application with all sorts of components, is that you can't load code during run-time easily. When angular bootstraps your DOM, it creates an injector with the configuration that was defined at that moment and you cannot add services, directives, controllers, etc. easily after the injector was created. This library allows you to download js/css/html code into a running angular app and will create a new injector for that "widget". Since each widget has its own injector, each widget has a different instance for services that they use. They can configure them how ever they like without any effect on any other widget or on the main application that hosts the widgets. Regardless, all widgets share the same DOM, so a widget create modal dialogs or whatever it likes. Widgets are simply added to the DOM using the ng-widget directive. The directive download all required files and creates the widget. Widgets can get information from the hosting application using the options attribute of the directive and they can report to the hosting application using widgetConfig.exportProperties and widgetConfig.reportError.

But that's just the start! Widgets can actually be full blown applications with their own router. (both angular-route and ui-router are supported) See the demo page for an example that uses angular-route to host three lazy loaded applications - one uses ui-router internally, one uses angular-route and the third displays some widget (widgets within widgets, whoa...)

See https://github.com/wix/angular-widget/blob/master/app/scripts/demo.js for an example of how you would typically configure a hosting application to run multiple lazy loaded applications on different routes.

Installation

Install using bower

bower install --save angular-widget

Include script tag in your html document.

<script type="text/javascript" src="bower_components/angular-widget/angular-widget.js"></script>

Add a dependency to your application module.

angular.module('myApp', ['angularWidget']);

Directive Usage

<ng-widget src="'demo'" options="{name: 'value'}"></ng-widget>

Arguments

Param Type Details
src string Name of widget to download. This is resolved to a module name, html file url and js/css file url list when the directive invokes widgets.getWidgetManifest(src) (more on this soon)
options (optional) object An object of options which might effect the behavior of the widget. The widget gets those options by calling widgetConfig.getOptions() (more on this soon)
delay (optional) number Well, that's pretty silly, but with widgets you sometimes want to let the user feel that the widget is actually loading. So you can add a delay with this param

Events

The directive emits the following events:

Param Details
widgetLoading Sent when the widget loading starts
widgetLoaded Sent when the widget is done loading. This happens when all the files were downloaded and the new DOM node was bootstrapped. In case the widget itself wants to postpone sending this event until it is done initializing, it can optionally call widgetConfig.exportProperties({loading: true} in a run block and then call widgetConfig.exportProperties({loading: false } when done
widgetError Sent when some download or bootstrap fails. Called also when the widget calls widgetConfig.reportErrror()
exportPropertiesUpdated Sent along with the updated properties when the widget calls widgetConfig.exportProperties()

The directive will reload the widget if it receives a reloadWidget event from a parent scope.

Service Usage (hosting application)

angular.module('myApp').config(function (widgetsProvider) {
  widgetsProvider.setManifestGenerator(['dep1', 'dep1', function (dep1, dep2) {
    return function (name) {
      return {
        module: name + 'Widget',
        config: [], //optional array of extra modules to load into the new injector
        priority: 1, //optional priority for conflicting generators
        html: 'views/' + name + '.html',
        files: [
          'scripts/controllers/' + name + '.js',
          'styles/' + name + '.css'
        ]
      };
    };
  }]);
})

Arguments

You must set the manifest generator function in order for the directive to work. This is how we can know for a specific plugin, what files should be loaded and with which module name to create the injector. Above you can see an example for a manifest generator, but you can do whatever you like. You can put both relative and absolute URL's, of course.

Note: In case requirejs is available in the global scope, it will be used to load the javascript files. So if your widget needs more than one js file, you can include requirejs and use AMD to load them.

You can actually set multiple manifest generators and they will be evaluated in the order that they were defined. So a generator is allowed to return undefined in case it simply wants a different generator to handle it. The way the generators responses are handled is that the last generator that didn't return undefined will be used, unless a different generator returned a result with higher priority.

Service Usage (widget)

In order to communicate with the hosting application, the widget uses the widgetConfig service. (the widget module always has a module dependency on angularWidget, if no such dependency exists, it will be added automatically during bootstrap)

Methods

Name Param Details
exportProperties properties (obj) Send information to the hosting application. The object sent in the param will extend previous calls, so you can send only the properties that have changed. The directive will emit a exportPropertiesUpdated event.
reportError N/A Report some kind of error to the hosting application. The directive will emit a widgetError event.
getOptions N/A Get the options object supplied by the object. The options will always have the same reference, so you can save it on the scope. A scope digest will be triggered automatically if the options change.

Sharing information between widgets

In some cases it might be needed to share some global state between widgets. When this global state changes you'll probably need to run a digest cycle in all widgets. $rootScope.$digest() will run the digest only in the injector which owns that $rootScope instance. To run $rootScope.$digest() on all $rootScope instances in all widgets, use widgtes.notifyWidgtes().

Also, if you want to broadcast an event on the $rootScope of all widgets, just call widgets.notifyWidgets(eventName, args, ...). It returns an array of the scope event that were dispatched.

Sharing services

As mentioned before, each widget has its very own injector, which means that each widget has its own service instances which are isolated from the services of other widgets and of the hosting application. A nice way to share information between widgets and the the hosting application is to have a shared service instance. So you can ask angular-widget to have the service shared pretty easily by running widgetsProvider.addServiceToShare(name, description) in a config section of the hosting application - name is the name of the service you want to share, that's pretty obvious, but description (optional) is a bit more difficult to explain.

It is important to remember that the widgets and hosting application do not share the same digest cycle, so if you are going to make a call to a shared service from a widget, you want to trigger a digest in all root scopes that share this service instance. You could just call widgets.notifyWidgets(), but an easier way would be to declare which methods of the shared service might change the state (no need to mention getters, for example) and have angular-widget do the rest for you. So description can be an array of such method names, or it can be an object where the method name is the key and the minimum amount of arguments is the value. The object option should be used when you have something like methods that behave as getters when they have no arguments and as setters when they have one arguments. (in this case you would pass {methodName: 1} as description)

BTW, one service that is shared by default in order for angular-widget to work is $location.

Sharing events

One more important option to share information between widgets and the main application are scope events. Since the widgets have a different injector, their root scope is isolated from scope events in different injectors, but this can easily be changed by adding widgetsProvider.addEventToForward(name) in a config section of the hosting application. This will make the ngWidget directive propagate events with this name to the widget's root scope. The widget may call preventDefault() on the event in order to prevent the default behavior of the original event.

BTW, one event that is shared by default is $locationChangeStart. This is in order to allow widgets to preventDefault() and display some "you have unsaved changes" dialog if they want to. If the user decides to continue, the widget can do something like $location.$$parse(absUrl). (absUrl is the first parameter passed along with the $locationChangeStart event) Calling $$parse will trigger a digest in the hosting application automatically as described in the previous section, since this service is shared by default.

How to use in the real world

This framework is best used by having a separate project for each widget. During development, the developer sees only his own widget. All widgets should be built in a consistent manner, usually with one concatenated minified .js and .css files.

License

The MIT License.

See LICENSE

angular-widget's People

Contributors

adri avatar alex5il avatar hesher avatar netanelgilad avatar ofirdagan avatar shahata 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  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

angular-widget's Issues

Define widget manifest inside widget / or lazy load dependencies

The current method of defining widgets requires that all definitions are within the hosting app. It would be useful to allow definitions within the widget itself.

Some example use cases are:

  • Your widget needs JS from cdn hosted sources - this JS can't be included in the combined 'scripts.js' file defined in the host manifest.
  • One widget depends on a few modules that are not included in the host applications config property.

Possible approaches:

  • Allow lazy loading of modules and files, similar to: https://github.com/ocombe/ocLazyLoad.
  • Recognize new manifests registered in widgets.
  • Add helper methods, similar to addServiceToShare that can be called from the config block of widget. for example: widgetsProvider.addFile('funky-thing.js'); and widgetsProvider.addModule(FunkyThing');. All files would need to resolve before the widget was loaded.

Updating $rootScope does not update hosting app's view

I'm trying to implement a service that exposes a global loading screen provided by the hosting app. The service works fine in the hosting app, in that my view is updated when I trigger the service from the hosting app. However, if I trigger the service from a widget, it seems the $rootScope I use to trigger the loading screen is updated, but the view tied to it is not. Plunker here: https://plnkr.co/edit/7hskrPayP2vyAkJEeIPX

You'll see if you use the buttons in App1, the Loading var in $rootScope is changed accordingly, and the view updates, but if you use the buttons from App2, the console logs confirm that the var does change but the view does not.

scope.apply with shared services

I'm testing angular-widget and like it so far. There are some problems I discovered though. One of them is that changes to variables in a widgets scopes are not applied automatically like they have been before.

In a widget controller:

$scope.items = [];
    $http.get('/api/items').success(function (data, status, headers, config) {
      $scope.items = data;
      $scope.$apply(); 
    });

I guess $scope.$apply() has to be called, because $http is shared? Afaik some angular services (like $http) automatically trigger an $apply() on the scope. But when services are shared, that might be a different scope?

Module error

Hi. When Im trying to do this few steps mentioned in 'readme' I have an error connected with name of module of my 'widget'. Do you know this issue? Or mayby somwhere exist some instructions how to start.

Options object is empty after getting it from widgetConfig

In my view

<ng-widget src="widget.Type" options="{name: 234}"></ng-widget>

In the widget

(function () {
    'use strict';

    var testWidget = angular.module('test-widget', ['angularWidget', 'shared']);

    testWidget.controller("testCtrl", function ($scope, configs, data, widgetConfig) {
        var options = widgetConfig.getOptions();
        //options is {}
    });
})();```

Asynchronous setManifestGenerator Function

Would be better if the Downloading and Bootstraping process is aligned with the asynchronous pattern...this way, if inside the function that resolves the manifest there is some asynchronous initialization stage (like reading a remote json file with apps information) developer can call to "done(manifest)" (passed by angular-widget) on the resolution of $http.get for example.

Access widget config in setManifestGenerator function on child app

This is it:

I have an application (lets say A) which is a widget, so It's loaded with a setGeneratorFunction defined in host App.
in turn, this application A also loads widgets, defined in a second setGeneratorFunction defined in config handler for A. this way, it loads the modules mod1, mod2 and mod3 of app A.
but the files needed for these modules follow a directory structure for which name of App A is needed, so i need to have access to app A "Name" (the path in manifest is formed with AppName + "/" + name)
The thing is that I can't push appName inside config section: I was planning to passs AppName as part of options object to widget A, but widgetConfig is not available in config....could you provide guidance ?

Be able to use relative URLS in Widget html

I'm starting the Host App in a directory of my web server, let's say "/hostApp", and then I have some Angular Micro Applications in apps/app1, apps/app2, apps/app3 under hostApp directory.
in the manifest, I reference the assets for each one of the apps like this:
html: "/apps/app1/views/index.html"
files: [ "/apps/app1/js/app.js", "/apps/app1/js/util.js"
This files load fine when boostraping the application. The thing is that inside index.html I have a couple of ng-include directives on which src attribute is specified relative to the index.html's path, actually the same directory: "./summary.html". But after index.html is loaded, the ng-include returns 404 because it's looking for "/hostApp/summary.html". is there a way to configure the base URL for that widget ?

Issue with ngAnimate

I'm trying to animate (via css transition) an element containing an ng-show directive. Works fine if the element is not in a widget, does not if in a widget. Do you have any experience/recommendations on using ngAnimate transitions within an angular-widget? Thanks much.

Sharing events from widget to hosted app is not working

Hi we are trying to forward an event that is emitted by a widget to the hosting app.
The steps that we follow were:

  1. Add widgetsProvider.addEventToForward('testEvent'); code to the hosting app configuration section.
  2. Add a $rootScope.$on('testEvent', function(event, data){alert('works')} to the hosting app code (in any controller section). In order to listen the event from the widget.
  3. Add a $rootScope.$emit('testEvent',''), code on widget application.

The event is not forwarded from the widget app to the hosted app :( .

We have tested with $emit and also with $boradcast on the $rootScope.

What is wrong with this? Is this behavior supported by the library?

Thanks!

Angular 2

Hey there!

I'm involved in a project using this module, and I'm interested in your thoughts on how an Angular 2 transition is going to work. From my understanding, this project is pretty tied to how things are currently loaded and managed in the angular 1.x runtime.
Are there any concerns that a transition to Angular 2 will be difficult/impossible, or is there a sense that those aspects of the internals will be similar enough that the process won't be too bad?

Any insight would be great!

Nested routers does not work

Or at least there is no clear example on how to do this....
I'm loading a widget within an ng-view placeholder. and that widget is loading an html that has a ui-view element.
For that widget I defined a route with a controller, that shows some table content on the ui-view....for a little while. after that, it all disapears. even the content of the ng-view dissapears...
I have only one microapp, but I want that microapp to manage its own routes....Am I doing something wrong ? do I have to load the dependencies in a different way ? It seems like a change on a state on the ui-router is affecting the external view....please advice.
This is the dom structure when I load the widget. the "main" state is defined and has a controller and a template (no errors when loading the template, and controller code executes normally)

<ng-view ng-if="appReady" class="ng-scope">
    <div class="widget ng-scope">
        <ng-widget src="src" options="options" delay="1" class="ng-isolate-scope">
            <div class="ng-binding ng-scope">
                <a class="btn-link" ui-sref="main" href="#/incidents/main">Information</a>
                <!-- uiView: undefined -->
                <ui-view class="ng-scope"></ui-view>
            </div>
        </ng-widget>
    </div>
</ng-view>

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.