Coder Social home page Coder Social logo

ngparty / ng-metadata Goto Github PK

View Code? Open in Web Editor NEW
355.0 355.0 46.0 1.11 MB

Angular 2 decorators and utils for Angular 1.x

Home Page: https://hotell.gitbooks.io/ng-metadata/content/

License: MIT License

TypeScript 97.54% HTML 2.16% CSS 0.16% Shell 0.14%
angular angularjs decorators migration ng-metadata typescript

ng-metadata's People

Contributors

aciccarello avatar aitboudad avatar artaommahe avatar david-gang avatar dmorosinotto avatar elmariofredo avatar hotell avatar jameshenry avatar lpcmedia avatar pauljeter avatar reppners avatar vandycknick 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

ng-metadata's Issues

replace ng1 {require: string[] | string } for directive DI with custom handling

requiring other directive controllers is really cumbersome via angular 1 ddo require: string | string[] property.
This is resolved in 1.5 via Map support for require: string | string[] | {[boundName:string]:any}, but only works withngModule.component`.

Current DI via @Inject has to rely on name provided for require array, which is pretty inconvenient to use ( your property has to have same name as directive ).

because those reasons, we need to require for other directive instances manually as follows:

  • require: ['ngModel'] => $element.controller('ngModel')
  • require: ['^ngModel'] => $element.controller('ngModel') || (while(notFound){ var notFound = $element.parent().controller('ngModel') })
  • require: ['^^ngModel'] => while(notFound){ var notFound = $element.parent().controller('ngModel') }

all of above should throw and Error if controller not found == same behaviour as ng1 require

  • error will not be thrown and property will be null if we use require: ['?ngModel']

migrate to typescript 1.8

  • this will allow us to extend global without hacks and will remove the need of import global.d.ts to ng-metadata/core during build

same directives with different require accessors are not injected properly

in angular it's possible to inject same type of directives by require directive on itself and on parent via:
{require:['ngModel','^^ngModel']}

If I try do that within ngMetadata constructor I've got unefined formProvider

class SomeAttrDirective{
constructor(
  @Inject('form') @Self() form,
  @Inject('form') @SkipSelf() parentForm
){}
}

docs and gh-pages

Maybe what this project need is a live (static) site. As landing page for this project and a marketing device for this project. (With marketing device I mean a link to a few nice pages that could be dropped in blog posts, ... :) )

I was thinking maybe you could use a gh-pages site to show the api docs, examples, introduction and getting started material, ...

I'm not sure what your opinion is but wouldn't it be nice to let something like jsdocs generate your api docs. Which could then be picked up by the gh-pages site?

I'm also not sure what your opinion is about how much docs should live in the actual source code.

Anyways I definitely want to help with these things. Just let me know what you think.

API approach - angular 2 like vs angular 1

how should it look and work like?

I see 2 approaches here:

and after thorough research a way to go is number 2!
See results

Angular 2 API mirror with component tree

  • uses compoment tree patterns as Angular 2
  • angular.module is created from every class form it's name so:
    • if you have :
@Component({})
class Foo(){}

behind the scenes it will:

  • create new module angular.module('foo',[]) and
  • register there a directive angular.module('foo').directive(makeSelector(Foo),makeDirective(Foo))
// user.component.ts
import {Component,Input} from 'ng-metadata';

@Component({
    selector: 'user',
    template: `
        <div>
            <h1>User detail !</h1>
            <ul><li>twitter: {{ ctrl.user.twitter }}</li></ul>
        </div>
    `
})
class User{

    @Input() user: {twitter:string}

    greet(){
        return `${this.user.twitter} says Hello yo!`;
    }

}

// user.component.spec.ts
import {User} from './user.component';
import {getControllerName, getModule} from 'ng-metadata';
describe('User Component', ()=>{

    beforeEeach(()=>{
        angular.mock.module(getModule(User));
    });

    var userCtrl;
    beforeEach(inject(($controller)=>{
        userCtrl = $controller(getControllerName(User),{},{user:{twitter:'foobar'}});
    }));
    it('should get passed user via input binding',()=>{
        expect(userCtrl.user).to.deep.equal(user:{twitter:'foobar'}))
    });
    it('should greet',()=>{
        expect(userCtrl.greet()).to.equal('foobar says Hello yo!');
    });

})

should translate to:

class User{ 
    static requiredModules = [];
    static selector = 'user';
    static template = `
        <div>
            <h1>User detail !</h1>
            <ul><li>twitter: {{ ctrl.user.twitter }}</li></ul>
        </div>
    `;
    static options = {
        bindToController: {
            userInput:'='
        }
    };
    user = null;
}
function userDirectiveFactory(){
    return {
        scope: {},
        bindToController: User.options.bindToController,
        controller: User,
        controllerAs: 'ctrl',
        template: App.template,
        link: (scope,element,attrs,ctrl)=>{
            // one way binding
            scope.$watch(()=>ctrl.userInput,(user)=>{
                ctrl.user = angular.copy(user)
            });

            if(angular.isFunction(ctrl.onInit)){
                ctrl.__proto__.onInit = angular.noop;
            }
            ctrl.onInit();
        }
    }
}
const ngModule = angular
    .module(User.name,User.requiredModules)
    .directive(makeSelector(User),userDirectiveFactory);

Root component:

// app.component.ts

import {Component,bootstrap} from 'ng-metadata';
import uiBootstrap from 'angular-ui-bootsrap';
import uiRouter from 'angular-ui-router';
import User from './user/user.component';
import UserList from './user/usersList.component';
import UpperCase from './utils/uppercase.pipe';


@Component({
    selector: 'app',
    template: `
        <div>
            <h1>Hello {{ ctrl.greet }} !</h1>
        </div>
    `,
    providers: [uiRouter, uiBootstrap],
    directives: [UserList, User],
    pipes: [UpperCase]
})
class App{

    greet: string = 'World';

}


bootstrap(App);


// should transform to:
import {makeSelector,bootstrap} from 'ng-metadata';
import uiBootstrap from 'angular-ui-bootsrap';
import uiRouter from 'angular-ui-router';
import User from './user/user.component';
import UserList from './user/usersList.component';
import UpperCase from './utils/uppercase.pipe';

class App{  
    static requiredModules = [
        uiRouter, uiBootstrap, UserList, User, UpperCase
    ];
    static selector = 'app';
    static template = `
        <div>
            <h1>Hello {{ ctrl.greet }} !</h1>
        </div>
    `;

    greet: string = 'World';    
}
function appDirectiveFactory(){
    return {
        scope: {},
        bindToController: {},
        controller: App,
        controllerAs: 'ctrl',
        template: App.template,
        link: (scope,element,attrs,ctrl)=>{
            if(angular.isFunction(ctrl.onInit)){
                ctrl.__proto__.onInit = angular.noop;
            }
            ctrl.onInit();
        }
    }
}
const ngModule = angular
    .module(App.name,App.requiredModules)
    .directive(makeSelector(App),appDirectiveFactory);

// bootstrap
document.addEventListener('DOMContentLoaded',(e)=>{
    angular.bootstrap(App.selector,[App.name]);
});

sugar via decorators but registering pieces via pure angular API

// user.component.ts
import {Component,Input,Output} from 'ng-metadata';
import {StateConfig} from 'ng-metadata/router';
import FriendsCmp from './friends/friends.component';

interface IUser{
    name: string,
    age: number,
    email: string,
    twitter:string
}

@Component({
    selector: 'user',
    template: `
        <div>
            <h1>User detail !</h1>
            <ul><li>twitter: {{ ctrl.user.twitter }}</li></ul>
            <form>
            <input ng-model="ctrl.user.name">
            <button ng-click="ctrl.changeUser(ctrl.user)">Change</button>
            </form>
                       <ui-view></ui-view>
        </div>
    `
})
@StateConfig({
        name:'user.friends', // child state 
        path: '/friends', // the same as url in uiRouter 
        component: FriendsCmp, // instead of template
        otherwise: '/' // the same as otherwise via $urlRouteProvider
})
export default class UserCmp{

    @Input() user: IUser;
    @Output() onChangeUser: (user)=>void;

    greet(){
        return `${this.user.twitter} says Hello yo!`;
    }
    changeUser(user){
        this.onChangeUser(user);
    }

}

// desugars to
export default class UserCmp{

    static selector = 'user';
    static template = `...`;
    static bindings = {
        user: '=',
        onChangeUSer: '&'
    };
    static $stateConfig = {
        name:'user.friends', // child state 
        path: '/friends', // the same as url in uiRouter 
        component: FriendsCmp, // instead of template
        otherwise: '/' // the
    };
    static options = {
        // here could be other angular 1 directive apis
        // terminal, priority etc...
    };

    user: IUser;
    onChangeUser: (user)=>void;

    greet(){
        return `${this.user.twitter} says Hello yo!`;
    }
    changeUser(user){
        this.onChangeUser(user);
    }
    onInit(){}
    onDestroy(){}

}

uppercase.pipe.ts

import {Pipe} from 'ng-metadata';

@Pipe({name:'uppercase'})
export default class Uppercase{
    transform(input:string){
        return angular.isString(input)
            ? input.toUpperCase()
            : input;
    }
}

// desugars to
export default class Uppercase{

    static name = 'uppercase';
    static pure = true;
    static $inject = [];
    constructor(){
    }
    transform(input:string){
        return angular.isString(input)
            ? input.toUpperCase()
            : input;
    }
}

helpMe.service.ts

import {Inject} from 'ng-metadata';

export default class HelpMe{

    constructor(@Inject('$log') private $log: ng.ILogService){}

    callForHelp(){
        this.$log.log('heelp');
    }
}

// desugars to:
export default class HelpMe{

    static $inject = ['$log'];
    constructor(private $log: ng.ILogService){}

    callForHelp(){
        this.$log.log('heelp');
    }
}

user.config.ts

runBlock.$inject = ['$log']
export function runBlock($log: ng.ILogService){
    $log.log('run executed!');
}

// in case you need config some providers
configure.$inject = ['$log']
export function configure($logProvider: ng.ILogProviderService){
    $logProvider.enable(true);
}

user.ts

import {makeSelector,makeComponent,makePipe,stringify,stateConfig} from 'ng-metadata';
import UserCmp from './user.component';
import Uppercase from './uppercase.pipe';
import HelpMe from './helpMe.service';
import {runBlock,configure} from './user.config'

export default const UserModule = angular
    .module('userModule',[])
    .run(runBlock)
    .config(configure)
    .config(registerState(UserCmp))
    .directive(makekeSelector(UserCmp),makeComponent(UserCmp))
    .service(stringify(HelpMe),HelpMe)
    .filter(stringify(Uppercase), makePipe(Uppercase))
    .name;

helpers

#makePipe:

function makePipe(Type){
  const isPure = Type.$inject.length === 0;

  filterFactory.$inject = '$injector';
  function filterFactory($injector){
    const pipeInstance = $injector.instantiate(Type);   
    return isPure ? pipeInstance.transform : pipeInstance.transform.bind(pipeInstance); 
  }
  return filterFactory;
}

#registerState

function registerStates(Type){

        function createHtmlElemString(Type){
            return `<${Type.selector}></${${Type.selector}}>`;
        }

        const states: any[] = Type.$stateConfig;
        stateConfig.$inject = ['$stateProvider'];
        function stateConfig($stateProvider){
            states.forEach((state)=>{
                $stateProvider.state(state.name,{
                    url: state.path,
                    template: createHtmlElemString(Type)
                });
            })
        }
  return stateConfig;
    }


Final thoughts:

support compile/link function for @Component and @Directive as static method on class

This will have one constraints, user won't be able to inject anything, just use locals which are:

  • raw dom tElement
  • tAttrs

It is already available from from within legacy property on @Component/@Directive decorator, but we should also support it as a static method on class

@Directive({selector:'[template-changer]'})
class MyDirective{
  static compile(tElement: ng.IAugmentedJquery, tAttrs: ng.IAttributes){
   // your compile logic
  }
}

implement makePipe utility function

just pure function which will create correct filter factory function

@Pipe({name:'camelcase'})
class CamelcasePipe{
  transform(txt: string, options?): any {
    return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
  }
}

// when called
import {makeFilter} from 'ng-metadata';
const filterFactory= makeFilter(CamelcasePipe);

expect(filterFactory).to.be.a('function');
expect( filterFactory('foo-bar') ).to.equal('fooBar'')

implement makeDirective utility function

just pure function which will create correct directive factory function

@Component({selector:'foo-bar'})
@View({template:`<h1>Hello</h1>`})
Class FooBar{
  onInit(){ console.log('hello foo-bar component init'); }
}

// when called
import {makeDirective} from 'ng-metadata';
const directiveFactory = makeDirective(FooBar);

expect(directiveFactory ).to.be.a('function');
expect( directiveFactory() ).to.deep.equal({
  scope:{},
  bindToController:{},
  controller:FooBar,
  controllerAs:'vm',
  require:['fooBar'],
  link: function postLink(scope,element,attrs,controllers,transclude){
    const [ownCtrl] = controllers;
      ownCtrl.onInit();
  },
  template:`<h1>Hello</h1>`
})

OnInit is not called from right place ( not all ctrls are resolved )

Not all required controllers are resolved correctly.
Well that's obvious because that behaviour is only supported since angular 1.5 within $onInit

we need to introduce again preLink and call it from there.

Lessons learned:
It was really a mistake to copy anything from ngForward:)

implement makeSelector utility function

just pure function which will create correct directive name from selector string

@Component({selector:'foo-bar'})
@View({template:`<h1>Hello</h1>`})
Class FooBar{
}

// when called
import {makeSelector} from 'ng-metadata';
const directiveName = makeSelector(FooBar);

expect(directiveName).to.equal('fooBar');

implement provide function

this function should be used for registering services/pipes/components/directives within angular container.
This will create name from provided Type as argument.
It has the same name as angular 2 provide but I didn't find better name for it in ng-metadata

import {MyPipe} from './pipes';
import {UserSvc} from './services';
import {UserCmp} from './components';
import {Notify} from './directives';
import {provide} from 'ng-metadata/ng-metadata';

const ngModule = angular.module('app',[])
  .filter(provide(MyPipe),makePipe(MyPipe))
  .service(provide(MySvc),MySvc)
  .component(provide(UserCmp), makeComponent(UserCmp))
  .directive(provide(Notify), makeDirective(Notify));

// so this registers the tokens within angular container

What should be generated:

  • if pipe name was my, provide(MyPipe) will generate my
  • if service isn't used with provide optional param { as: 'foo' } it will generate userSvc
  • if component selector was user, provide(UserCmp) will generate userCmp
  • if directive selector was [notify-me], provide(Norify) will generate notifyMe

implement @Attr property decorator

@Attr() title: string should generate {bindToController:{title:'@'}}

this shouldn't be used to much because this is not in angular 2.
use @Input instead.

We need this for legacy code

Support optional inputs and outputs specified via @Input/@Output + @Optional decorators

First off - brilliant project, thanks for all the hard work! I am already successfully using this on a large Angular 1.x app.

Is there any particular reason why optional inputs and outputs are not supported via the decorator style? I.e. The DDO syntax of [..scope/bindToController..]: { myOptionalInput: '=?', myOptionalOuput: '&?' }

If not it would make a great addition!

Many thanks.

refactor decorators

refactor DI decorators to cleaner implementation, something like angular 2 does.

Now every parameter decorator is too smart and exposes awkward behaviour ( you cannot write multiple param decorators as you want ).

we need to just register the instance of decorator to some class metadata property and then register proper logic within angular container registration with provide,makePipe,makeDirective and so on.

POC

import {Inject,Host, Component, Output, Input} from 'di';
import {NgModel} from 'directives';
import {OtherService} from 'services';

@Component({
  selector:'my-cmp',
  template:'hello'
})
export class MyCmp{

  @Input() name: string;
  @Output() onChange: Function;

  constructor(
    @Inject('$log') private $log: $log,
    @Inject(OtherService) private otherService: OtherService,
    @Host() @Inject('ngModel') private ngModel: NgModel
  ){}

}

expect(MyCmp._mPropMetadata).to.deep.equal({
  name: [ InputInstance ],
  onChange: [ OutputInstance ]
});
expect(MyCmp._mParameters).to.deep.equal([
  [ InjectInstance('$log') ],
  [ InjectInstance('otherService') ],
  [ InjectInstance('ngModel'), HostInstance ]
]);
expect(MyCmp._mAnnotations).to.deep.equal([
  InjectableInstance
]);

and angular container registration:

import * as angular from 'angular';
import {provide, makeDirectvie} from 'di';
import {MyService} from 'myService';

angular.module('myApp',[])
  .directive(provide(MyCmp), makeDirective(MyCmp));

expect(MyCmp.$inject).to.deep.equal([
  '$log', 'otherService'
  // NOTE: there is no ngModel because that's used for directives require
]);

expect(makeDirective(MyCmp)()).to.deep.equal({
scope:{},
bindToController: { name: '=', onChange: '&' },
controllerAs:'ctrl',
controller: MyCmp,
template: 'hello',
require: ['myCmp','ngModel'],
link: function postLink(scope,element,attrs,controllers){
  const [ownCtrl, ...requiredCtrls] = controllers;
  this.require.slice(1).forEach((ctrlName,idx)=>{
    ownCtrl[ ctrlName ] = requiredCtrls[idx];

   if(isFunction(ownCtrl.ngAfterContentInit)){
     ownCtrl.ngAfterContentInit();
   }

  });
}
})

conclusion

  • provide will only handle $inject property, it's the only method which will mutate Type.
  • makePipe && makeDirective will just create factory functions for directive/filter from provided metadata, so no Type mutation
  • if @Inject is not used on constructor it should implicitly create $inject on that method and add there needed tokens ( this is use case for Provider which has $get method which needs DI )

dependant issues: #33

create di/decorators,metadata ( constructor injection )

We should create di parameter decorators by leveraging decorator factories

We should create all supported Metadata classes for DI constructor injection, like Angular 2 has:

  • @Inject
  • @Injectable
  • @Host
  • @Optional
  • @Self
  • @SkipSelf

depends on: #33

hook up onDestroy

if component/directive implements OnDestroy interface we should invoke this method on scope.$destroy from within postLink function.

implement ngOnChanges life cycle

So you don't have to use $scope never ever again! :)

it should work like angular 2 https://angular.io/docs/ts/latest/api/core/OnChanges-interface.html

@Component({
  selector: 'my-cmp',
  template: `<p>myProp = {{$ctrl.myProp}}</p>`
})
class MyComponent implements OnChanges {
  @Input() myProp: any;

  ngOnChanges(changes: {[propName: string]: SimpleChange}) {
    console.log('ngOnChanges - myProp = ' + changes['myProp'].currentValue);
  }

}

under the hood it should use probably $scope.watchGroup for perf reasons

Refactor codebase

There is a lot of mess inside one file - especially directives.ts

We need to separate the logic do small logical module chunks and export only correct public API interface for consumer

create logo

every project should have cool logo ๐Ÿ’ƒ

@Inject() with reference doesn't resolves directive name for ddo require correctly

import {Directive,Inject,Host} from 'ng-metadata/core';

@Directive({selector:'[my-validator]'})
class MyValidatorDirective{}

@Directive({selector:'[my-tester]'})
class MyTesterDirective{
  constructor(
    @Inject('ngModel') @Host() private ngModel,
    @Inject(MyValidatorDirective) @Host() private myValidator
  ){}
}

creates { require: ['myTester','^ngModel','function MyValidatorDirective'] }
instead of { require: ['myTester','^ngModel','^myValidator'] }

problem is probably in DirectiveResolver which just returns token

create @StateConfig

We need to register states for component via @StateConfig similar like ng-forward does

API:

@StateConfig will provide all options which has uiRouter for $stateParams except those, which can be injectable. Injectable needs to be provided by child component which also makes sense to encapsulate those things together, because they are related.

property description
name unique state name
url A url fragment with optional parameters.
component Reference to Child component which should be activated on this state
template? will override component.selector to custom template
templateUrl? will override component.selector to custom template
parent? uiRouter specific
views? uiRouter named views
abstract? uiRouter abstractState
reloadOnSearch? uiRouter specific
params? uiRouter specific

How to resolve:

because resolve can be injectable, define static methods on your child component with @Resolve method decorator.

You shouldn't be using resolve anyway!

so you can end up with

@Component({})
class Child{

  @Resolve()
  static boo(){}

  @Resolve()
  static usesDi(@Inject('$http') $http){ return $http.get('/get/me'); }

 // and you can get those values from $state
  constructor(@Inject('$state') $state) {

       // get resolve property
        const {boo, usesDi} = $state.$current.locals;

    }

}

which will transform to:

Child.resolve = { 
  boo: function(){},
 // this is properly annotated via $inject, behind the scenes
  usesDi: function($http){ return $http.get('/get/me'); }
}

and this will be used on parent stateConfig creation

How to onExit | onEnter:

because onEnter,onExit can be injectable, implement OnExit,OnEnter interface on your child component and use `@Inject if there are any Injectables.
see #28 for more

Example

import { Component, StateConfig, provide, makeDirective } from 'ng-metadata/ng-metadata';
import * as angular from 'angular';
import * as uiRouter from 'ui-router';

@Component({
    selector: 'child-a',
    template: '{{ ctrl.text }}' // will be 'A resolved!'
})
class ChildA {

    text: string;

   @Resolve()
   static resolveA() {
        return 'A resolved!';
    }

    constructor(@Inject('$state') $state) {

       // get resolve property
        this.text = $state.$current.locals.resolveA;

    }

}

@Component({
    selector: 'child-z',
    template: 'Im child Z '
})
class ChildZ {}

@Component({
    selector: 'parent',
    template: `<ui-view></ui-view>`
})
@StateConfig([
    { name: 'childA', url: '/childA', component: ChildA },
    { name: 'childZ', url: '/childZ', component: ChildZ }
])
class Parent {}


angular.module('app',[uiRouter])
  .config(provide(Parent))
  .directive(provide(Parent), makeDirective(Parent))
  .directive(provide(ChildA), makeDirective(ChildA))
  .directive(provide(ChildZ), makeDirective(ChildZ));

create project structure

/src  - source files
/test - tests
/dist - directory where build project will be generated
/scripts - helper utils, systemJs bundler scripts etc
/playground - showcase
package.json
.gitignore
Readme.md
karma.conf.js
tsconfig.js

create build/bundler script

Project will be powered by SystemJs + npm. So we don't have bundler which is included in jspm.

We need to build our own script which can be moved to separate CLI tool, by leveraging systemjs-builder

implement bootstrap utility function

this should just encapsulate manual angular bootstrap to mirror kind of angular 2.

usage:

import {bootstrap} from 'ng-metadata';

const ngModule  = angular.module('app',[]);

bootstrap(ngModule);

is just replacement for:

const ngModule  = angular.module('app',[]);

angular.element( document ).ready( ()=> {
  angular.bootstrap( document, [ ngModule.name ], {
    strictDi: true
  } )
} );

di: allow passing references to @Inject

we support just Magic strings for Inject

import {Inject} from 'ng-metadata/ng-metadata';

class MySvc{}

class FooSvc{
  constructor(@Inject('mySvc', mySvc))
}

It would be nice to get rid of them and use references like in Angular 2

import {Inject} from 'ng-metadata/ng-metadata';

class MySvc{}

class FooSvc{
  constructor(@Inject(MySvc, mySvc))
}

create util/decorators

We should create generic factory methods for decorators creation like Angular 2 folks do

implement @ViewChildren/@ViewChild and @ContentChildren/@ContentChild property DI decorators

POC:

https://jsbin.com/kapecek/16/edit?html,js,output

  • this cannot be implemented so easily as in angular 2 because angular 1 will never be ng2 :)
  • so we can't control digest cycle properly and children rendering so we have to do following:
  • custom logic will be handled by user
  • if user knows that he want's to query component/directives which are not inside:
    • ng-repeat
    • ng-switch
    • ng-if
      he doesn't have to do anything
  • if one of queried child is within previously mentioned constructs, user needs to call ngChildrenChanged() from controller which is parent of those children. Also user needs to call this method within $scope.$on('$destroy', callback) to properly clean up GC.

For component template(view):

https://angular.io/docs/ts/latest/api/core/ViewChildren-var.html
https://angular.io/docs/ts/latest/api/core/ViewChild-var.html

For transclusion child:

https://angular.io/docs/ts/latest/api/core/ContentChildren-var.html
https://angular.io/docs/ts/latest/api/core/ContentChild-var.html

DI via typescript type annotations and reflect-metadata design:paramtypes

Currently we support DI only with @Inject parameter decorators which is not very convenient.

We need to allow same UX for DI as angular 2 via typescript annotations

  • add reflect-metadata library
  • handle @Inject via design:paramtypes under the hood

current state:

@Injectable()
class MyService{
  constructor(@Inject(Logger) private logger: Logger){}
}

demand:

@Injectable()
class MyService{
  constructor(private logger: Logger){}
}

one way data binding by default

Use one way data binding only and by default as angular 1.5 uses

angular/angular.js@4ac23c0#diff-a732922b631efed1b9f33a24082ae0dbL3037

angular doesn't copies those parent values, so if we have object and modify a property it will be mutated on parent, which is not very convenient. We wanna push best practices and stay immutable, so we will copy @Input props via angular.copy.

Although we should keep fallback for people which really need 2 way binding:

// one way only
@Component({ 
  selector: 'hero', 
  template:`hello`
})
class HeroComponent{
  @Input() name: string;
  @Input() skills: string[];
}
// two-way fallback via alias only
@Component({ 
  selector: 'hero', 
  template:`hello`
})
class HeroComponent{
  @Input('=') name: string;
  @Input('=') skills: string[];
}

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.