UNPKG

angular2

Version:

Angular 2 - a web framework for modern web apps

525 lines 21.4 kB
'use strict';var angular2_1 = require('angular2/angular2'); var application_common_1 = require('angular2/src/core/application_common'); var application_ref_1 = require('angular2/src/core/application_ref'); var compiler_1 = require('angular2/src/compiler/compiler'); var async_1 = require('angular2/src/facade/async'); var metadata_1 = require('./metadata'); var util_1 = require('./util'); var constants_1 = require('./constants'); var downgrade_ng2_adapter_1 = require('./downgrade_ng2_adapter'); var upgrade_ng1_adapter_1 = require('./upgrade_ng1_adapter'); var angular = require('./angular_js'); var upgradeCount = 0; /** * Use `UpgradeAdapter` to allow AngularJS v1 and Angular v2 to coexist in a single application. * * The `UpgradeAdapter` allows: * 1. creation of Angular v2 component from AngularJS v1 component directive * (See [UpgradeAdapter#upgradeNg1Component()]) * 2. creation of AngularJS v1 directive from Angular v2 component. * (See [UpgradeAdapter#downgradeNg2Component()]) * 3. Bootstrapping of a hybrid Angular application which contains both of the frameworks * coexisting in a single application. * * ## Mental Model * * When reasoning about how a hybrid application works it is useful to have a mental model which * describes what is happening and explains what is happening at the lowest level. * * 1. There are two independent frameworks running in a single application, each framework treats * the other as a black box. * 2. Each DOM element on the page is owned exactly by one framework. Whichever framework * instantiated the element is the owner. Each framework only updates/interacts with its own * DOM elements and ignores others. * 3. AngularJS v1 directives always execute inside AngularJS v1 framework codebase regardless of * where they are instantiated. * 4. Angular v2 components always execute inside Angular v2 framework codebase regardless of * where they are instantiated. * 5. An AngularJS v1 component can be upgraded to an Angular v2 component. This creates an * Angular v2 directive, which bootstraps the AngularJS v1 component directive in that location. * 6. An Angular v2 component can be downgraded to an AngularJS v1 component directive. This creates * an AngularJS v1 directive, which bootstraps the Angular v2 component in that location. * 7. Whenever an adapter component is instantiated the host element is owned by the the framework * doing the instantiation. The other framework then instantiates and owns the view for that * component. This implies that component bindings will always follow the semantics of the * instantiation framework. The syntax is always that of Angular v2 syntax. * 8. AngularJS v1 is always bootstrapped first and owns the bottom most view. * 9. The new application is running in Angular v2 zone, and therefore it no longer needs calls to * `$apply()`. * * ### Example * * ``` * var adapter = new UpgradeAdapter(); * var module = angular.module('myExample', []); * module.directive('ng2', adapter.downgradeNg2Component(Ng2)); * * module.directive('ng1', function() { * return { * scope: { title: '=' }, * template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)' * }; * }); * * * @Component({ * selector: 'ng2', * inputs: ['name'], * template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)', * directives: [adapter.upgradeNg1Component('ng1')] * }) * class Ng2 { * } * * document.body.innerHTML = '<ng2 name="World">project</ng2>'; * * adapter.bootstrap(document.body, ['myExample']).ready(function() { * expect(document.body.textContent).toEqual( * "ng2[ng1[Hello World!](transclude)](project)"); * }); * ``` */ var UpgradeAdapter = (function () { function UpgradeAdapter() { /* @internal */ this.idPrefix = "NG2_UPGRADE_" + upgradeCount++ + "_"; /* @internal */ this.upgradedComponents = []; /* @internal */ this.downgradedComponents = {}; /* @internal */ this.providers = []; } /** * Allows Angular v2 Component to be used from AngularJS v1. * * Use `downgradeNg2Component` to create an AngularJS v1 Directive Definition Factory from * Angular v2 Component. The adapter will bootstrap Angular v2 component from within the * AngularJS v1 template. * * ## Mental Model * * 1. The component is instantiated by being listed in AngularJS v1 template. This means that the * host element is controlled by AngularJS v1, but the component's view will be controlled by * Angular v2. * 2. Even thought the component is instantiated in AngularJS v1, it will be using Angular v2 * syntax. This has to be done, this way because we must follow Angular v2 components do not * declare how the attributes should be interpreted. * * ## Supported Features * * - Bindings: * - Attribute: `<comp name="World">` * - Interpolation: `<comp greeting="Hello {{name}}!">` * - Expression: `<comp [name]="username">` * - Event: `<comp (close)="doSomething()">` * - Content projection: yes * * ### Example * * ``` * var adapter = new UpgradeAdapter(); * var module = angular.module('myExample', []); * module.directive('greet', adapter.downgradeNg2Component(Greeter)); * * @Component({ * selector: 'greet', * template: '{{salutation}} {{name}}! - <ng-content></ng-content>' * }) * class Greeter { * @Input() salutation: string; * @Input() name: string; * } * * document.body.innerHTML = * 'ng1 template: <greet salutation="Hello" [name]="world">text</greet>'; * * adapter.bootstrap(document.body, ['myExample']).ready(function() { * expect(document.body.textContent).toEqual("ng1 template: Hello world! - text"); * }); * ``` */ UpgradeAdapter.prototype.downgradeNg2Component = function (type) { this.upgradedComponents.push(type); var info = metadata_1.getComponentInfo(type); return ng1ComponentDirective(info, "" + this.idPrefix + info.selector + "_c"); }; /** * Allows AngularJS v1 Component to be used from Angular v2. * * Use `upgradeNg1Component` to create an Angular v2 component from AngularJS v1 Component * directive. The adapter will bootstrap AngularJS v1 component from within the Angular v2 * template. * * ## Mental Model * * 1. The component is instantiated by being listed in Angular v2 template. This means that the * host element is controlled by Angular v2, but the component's view will be controlled by * AngularJS v1. * * ## Supported Features * * - Bindings: * - Attribute: `<comp name="World">` * - Interpolation: `<comp greeting="Hello {{name}}!">` * - Expression: `<comp [name]="username">` * - Event: `<comp (close)="doSomething()">` * - Transclusion: yes * - Only some of the features of * [Directive Definition Object](https://docs.angularjs.org/api/ng/service/$compile) are * supported: * - `compile`: not supported because the host element is owned by Angular v2, which does * not allow modifying DOM structure during compilation. * - `controller`: supported. (NOTE: injection of `$attrs` and `$transclude` is not supported.) * - `controllerAs': supported. * - `bindToController': supported. * - `link': supported. (NOTE: only pre-link function is supported.) * - `name': supported. * - `priority': ignored. * - `replace': not supported. * - `require`: supported. * - `restrict`: must be set to 'E'. * - `scope`: supported. * - `template`: supported. * - `templateUrl`: supported. * - `terminal`: ignored. * - `transclude`: supported. * * * ### Example * * ``` * var adapter = new UpgradeAdapter(); * var module = angular.module('myExample', []); * * module.directive('greet', function() { * return { * scope: {salutation: '=', name: '=' }, * template: '{{salutation}} {{name}}! - <span ng-transclude></span>' * }; * }); * * module.directive('ng2', adapter.downgradeNg2Component(Ng2)); * * @Component({ * selector: 'ng2', * template: 'ng2 template: <greet salutation="Hello" [name]="world">text</greet>' * directives: [adapter.upgradeNg1Component('greet')] * }) * class Ng2 { * } * * document.body.innerHTML = '<ng2></ng2>'; * * adapter.bootstrap(document.body, ['myExample']).ready(function() { * expect(document.body.textContent).toEqual("ng2 template: Hello world! - text"); * }); * ``` */ UpgradeAdapter.prototype.upgradeNg1Component = function (name) { if (this.downgradedComponents.hasOwnProperty(name)) { return this.downgradedComponents[name].type; } else { return (this.downgradedComponents[name] = new upgrade_ng1_adapter_1.UpgradeNg1ComponentAdapterBuilder(name)).type; } }; /** * Bootstrap a hybrid AngularJS v1 / Angular v2 application. * * This `bootstrap` method is a direct replacement (takes same arguments) for AngularJS v1 * [`bootstrap`](https://docs.angularjs.org/api/ng/function/angular.bootstrap) method. Unlike * AngularJS v1, this bootstrap is asynchronous. * * ### Example * * ``` * var adapter = new UpgradeAdapter(); * var module = angular.module('myExample', []); * module.directive('ng2', adapter.downgradeNg2Component(Ng2)); * * module.directive('ng1', function() { * return { * scope: { title: '=' }, * template: 'ng1[Hello {{title}}!](<span ng-transclude></span>)' * }; * }); * * * @Component({ * selector: 'ng2', * inputs: ['name'], * template: 'ng2[<ng1 [title]="name">transclude</ng1>](<ng-content></ng-content>)', * directives: [adapter.upgradeNg1Component('ng1')] * }) * class Ng2 { * } * * document.body.innerHTML = '<ng2 name="World">project</ng2>'; * * adapter.bootstrap(document.body, ['myExample']).ready(function() { * expect(document.body.textContent).toEqual( * "ng2[ng1[Hello World!](transclude)](project)"); * }); * ``` */ UpgradeAdapter.prototype.bootstrap = function (element, modules, config) { var _this = this; var upgrade = new UpgradeAdapterRef(); var ng1Injector = null; var platformRef = angular2_1.platform(); var applicationRef = platformRef.application([ application_ref_1.applicationCommonProviders(), application_common_1.applicationDomProviders(), compiler_1.compilerProviders(), angular2_1.provide(constants_1.NG1_INJECTOR, { useFactory: function () { return ng1Injector; } }), angular2_1.provide(constants_1.NG1_COMPILE, { useFactory: function () { return ng1Injector.get(constants_1.NG1_COMPILE); } }), this.providers ]); var injector = applicationRef.injector; var ngZone = injector.get(angular2_1.NgZone); var compiler = injector.get(angular2_1.Compiler); var delayApplyExps = []; var original$applyFn; var rootScopePrototype; var rootScope; var protoViewRefMap = {}; var ng1Module = angular.module(this.idPrefix, modules); var ng1compilePromise = null; ng1Module.value(constants_1.NG2_INJECTOR, injector) .value(constants_1.NG2_ZONE, ngZone) .value(constants_1.NG2_COMPILER, compiler) .value(constants_1.NG2_PROTO_VIEW_REF_MAP, protoViewRefMap) .value(constants_1.NG2_APP_VIEW_MANAGER, injector.get(angular2_1.AppViewManager)) .config([ '$provide', function (provide) { provide.decorator(constants_1.NG1_ROOT_SCOPE, [ '$delegate', function (rootScopeDelegate) { rootScopePrototype = rootScopeDelegate.constructor.prototype; if (rootScopePrototype.hasOwnProperty('$apply')) { original$applyFn = rootScopePrototype.$apply; rootScopePrototype.$apply = function (exp) { return delayApplyExps.push(exp); }; } else { throw new Error("Failed to find '$apply' on '$rootScope'!"); } return rootScope = rootScopeDelegate; } ]); } ]) .run([ '$injector', '$rootScope', function (injector, rootScope) { ng1Injector = injector; async_1.ObservableWrapper.subscribe(ngZone.onTurnDone, function (_) { ngZone.run(function () { return rootScope.$apply(); }); }); ng1compilePromise = upgrade_ng1_adapter_1.UpgradeNg1ComponentAdapterBuilder.resolve(_this.downgradedComponents, injector); } ]); angular.element(element).data(util_1.controllerKey(constants_1.NG2_INJECTOR), injector); ngZone.run(function () { angular.bootstrap(element, [_this.idPrefix], config); }); Promise.all([this.compileNg2Components(compiler, protoViewRefMap), ng1compilePromise]) .then(function () { ngZone.run(function () { if (rootScopePrototype) { rootScopePrototype.$apply = original$applyFn; // restore original $apply while (delayApplyExps.length) { rootScope.$apply(delayApplyExps.shift()); } upgrade._bootstrapDone(applicationRef, ng1Injector); rootScopePrototype = null; } }); }, util_1.onError); return upgrade; }; /** * Adds a provider to the top level environment of a hybrid AngularJS v1 / Angular v2 application. * * In hybrid AngularJS v1 / Angular v2 application, there is no one root Angular v2 component, * for this reason we provide an application global way of registering providers which is * consistent with single global injection in AngularJS v1. * * ### Example * * ``` * class Greeter { * greet(name) { * alert('Hello ' + name + '!'); * } * } * * @Component({ * selector: 'app', * template: '' * }) * class App { * constructor(greeter: Greeter) { * this.greeter('World'); * } * } * * var adapter = new UpgradeAdapter(); * adapter.addProvider(Greeter); * * var module = angular.module('myExample', []); * module.directive('app', adapter.downgradeNg2Component(App)); * * document.body.innerHTML = '<app></app>' * adapter.bootstrap(document.body, ['myExample']); *``` */ UpgradeAdapter.prototype.addProvider = function (provider) { this.providers.push(provider); }; /** * Allows AngularJS v1 service to be accessible from Angular v2. * * * ### Example * * ``` * class Login { ... } * class Server { ... } * * @Injectable() * class Example { * constructor(@Inject('server') server, login: Login) { * ... * } * } * * var module = angular.module('myExample', []); * module.service('server', Server); * module.service('login', Login); * * var adapter = new UpgradeAdapter(); * adapter.upgradeNg1Provider('server'); * adapter.upgradeNg1Provider('login', {asToken: Login}); * adapter.addProvider(Example); * * adapter.bootstrap(document.body, ['myExample']).ready((ref) => { * var example: Example = ref.ng2Injector.get(Example); * }); * * ``` */ UpgradeAdapter.prototype.upgradeNg1Provider = function (name, options) { var token = options && options.asToken || name; this.providers.push(angular2_1.provide(token, { useFactory: function (ng1Injector) { return ng1Injector.get(name); }, deps: [constants_1.NG1_INJECTOR] })); }; /** * Allows Angular v2 service to be accessible from AngularJS v1. * * * ### Example * * ``` * class Example { * } * * var adapter = new UpgradeAdapter(); * adapter.addProvider(Example); * * var module = angular.module('myExample', []); * module.factory('example', adapter.downgradeNg2Provider(Example)); * * adapter.bootstrap(document.body, ['myExample']).ready((ref) => { * var example: Example = ref.ng1Injector.get('example'); * }); * * ``` */ UpgradeAdapter.prototype.downgradeNg2Provider = function (token) { var factory = function (injector) { return injector.get(token); }; factory.$inject = [constants_1.NG2_INJECTOR]; return factory; }; /* @internal */ UpgradeAdapter.prototype.compileNg2Components = function (compiler, protoViewRefMap) { var _this = this; var promises = []; var types = this.upgradedComponents; for (var i = 0; i < types.length; i++) { promises.push(compiler.compileInHost(types[i])); } return Promise.all(promises).then(function (protoViews) { var types = _this.upgradedComponents; for (var i = 0; i < protoViews.length; i++) { protoViewRefMap[metadata_1.getComponentInfo(types[i]).selector] = protoViews[i]; } return protoViewRefMap; }, util_1.onError); }; return UpgradeAdapter; })(); exports.UpgradeAdapter = UpgradeAdapter; function ng1ComponentDirective(info, idPrefix) { directiveFactory.$inject = [constants_1.NG2_PROTO_VIEW_REF_MAP, constants_1.NG2_APP_VIEW_MANAGER, constants_1.NG1_PARSE]; function directiveFactory(protoViewRefMap, viewManager, parse) { var protoView = protoViewRefMap[info.selector]; if (!protoView) throw new Error('Expecting ProtoViewRef for: ' + info.selector); var idCount = 0; return { restrict: 'E', require: constants_1.REQUIRE_INJECTOR, link: { post: function (scope, element, attrs, parentInjector, transclude) { var domElement = element[0]; var facade = new downgrade_ng2_adapter_1.DowngradeNg2ComponentAdapter(idPrefix + (idCount++), info, element, attrs, scope, parentInjector, parse, viewManager, protoView); facade.setupInputs(); facade.bootstrapNg2(); facade.projectContent(); facade.setupOutputs(); facade.registerCleanup(); } } }; } return directiveFactory; } /** * Use `UgradeAdapterRef` to control a hybrid AngularJS v1 / Angular v2 application. */ var UpgradeAdapterRef = (function () { function UpgradeAdapterRef() { /* @internal */ this._readyFn = null; this.ng1RootScope = null; this.ng1Injector = null; this.ng2ApplicationRef = null; this.ng2Injector = null; } /* @internal */ UpgradeAdapterRef.prototype._bootstrapDone = function (applicationRef, ng1Injector) { this.ng2ApplicationRef = applicationRef; this.ng2Injector = applicationRef.injector; this.ng1Injector = ng1Injector; this.ng1RootScope = ng1Injector.get(constants_1.NG1_ROOT_SCOPE); this._readyFn && this._readyFn(this); }; /** * Register a callback function which is notified upon successful hybrid AngularJS v1 / Angular v2 * application has been bootstrapped. * * The `ready` callback function is invoked inside the Angular v2 zone, therefore it does not * require a call to `$apply()`. */ UpgradeAdapterRef.prototype.ready = function (fn) { this._readyFn = fn; }; /** * Dispose of running hybrid AngularJS v1 / Angular v2 application. */ UpgradeAdapterRef.prototype.dispose = function () { this.ng1Injector.get(constants_1.NG1_ROOT_SCOPE).$destroy(); this.ng2ApplicationRef.dispose(); }; return UpgradeAdapterRef; })(); exports.UpgradeAdapterRef = UpgradeAdapterRef; //# sourceMappingURL=upgrade_adapter.js.map