代码之家  ›  专栏  ›  技术社区  ›  georgeawg

新AngularJS ng ref指令的陷阱

  •  8
  • georgeawg  · 技术社区  · 6 年前

    AngularJS V1.7.1的发布 * 介绍新的 ng-ref directive . 虽然这个新指令允许用户轻松地做某些事情,但我看到了滥用和问题的巨大潜力。

    这个 ng-ref 属性告诉AngularJS在当前作用域上发布组件的控制器。这对于让音频播放器等组件将其API公开给同级组件非常有用。它的播放和停止控件可以很容易地访问。

    第一个问题是玩家控制是 undefined 在里面 $onInit 控制器的功能。

    Initial vm.pl = undefined  <<<< UNDEFINED
    Sample = [true,false]
    

    对于依赖于可用数据的代码, 我们怎么解决这个问题?

    The DEMO

    angular.module("app",[])
    .controller("ctrl", class ctrl {
      constructor() {
        console.log("construct")
      }
      $onInit() {
        console.log("onInit", this.pl);
        this.initPL = this.pl || 'undefined';
        this.sample = this.pl || 'undefined';
        this.getSample = () => {
          this.sample = `[${this.pl.box1},${this.pl.box2}]`;
        }
      }
    })
    .component("player", {
      template: `
        <fieldset>
          $ctrl.box1={{$ctrl.box1}}<br>
          $ctrl.box2={{$ctrl.box2}}<br>
          <h3>Player</h3>
        </fieldset>
      `,
      controller: class player {
        constructor() {
          console.log("player",this);
        }
        $onInit() {
          console.log("pl.init", this)
          this.box1 = true;
          this.box2 = false;
        }
      },
    })
    <script src="//unpkg.com/angular@1.7.1/angular.js"></script>
    <body ng-app="app" ng-controller="ctrl as vm">
        Initial vm.pl = {{vm.initPL}}<br>
        Sample = {{vm.sample}}<br>
        <button ng-click="vm.getSample()">Get Sample</button>
        <br>
        <input type="checkbox" ng-model="vm.pl.box1" />
          Box1 pl.box1={{vm.pl.box1}}<br>
        <input type="checkbox" ng-model="vm.pl.box2" />
          Box2 pl.box2={{vm.pl.box2}}<br>
        <br>
        <player ng-ref="vm.pl"></player>
    </body>
    2 回复  |  直到 5 年前
        1
  •  3
  •   georgeawg    6 年前

    引用components controller并不是什么新鲜事,以前的指令允许它,这根本不是问题,必须要有这样的特性, ng-ref 只是一个帮助您从模板侧执行此操作的工具(与Angular2+执行此操作的方式相同)。

    不过,如果您需要准备好子组件,则应该使用 $postLink() 而不是 $onInit . $postLink 在组件与子组件链接后调用,这意味着 天然气参考 当它被调用时就准备好了。

    所以你要做的就是 onInit 就像这样:

    ̶$̶o̶n̶I̶n̶i̶t̶(̶)̶ ̶{̶
    $postLink() {
        console.log("onInit", this.pl);
        this.initPL = this.pl || 'undefined';
        this.sample = this.pl || 'undefined';
        this.getSample = () => {
          this.sample = `[${this.pl.box1},${this.pl.box2}]`;
        }
    }
    

    $postLink() -在链接此控制器的元素及其子元素后调用。与post-link函数类似,此钩子可用于设置DOM事件处理程序和直接进行DOM操作。请注意,包含templateUrl指令的子元素将不会被编译和链接,因为它们正在等待其模板异步加载,并且它们自己的编译和链接已挂起,直到发生这种情况。这个钩子可以被认为类似于 ngAfterViewInit ngAfterContentInit 有角度的钩子。由于AngularJS的编译过程非常不同,因此没有直接映射,升级时应小心。

    裁判。: Understanding Components

    完整的工作片段可以在下面找到(我删除了所有 console.log 为了让它更清楚):

    angular.module("app",[])
    .controller("ctrl", class ctrl {
      constructor() {
        //console.log("construct")
      }
      $postLink() {
        //console.log("onInit", this.pl);
        this.initPL = this.pl || 'undefined';
        this.sample = this.pl || 'undefined';
        this.getSample = () => {
          this.sample = `[${this.pl.box1},${this.pl.box2}]`;
        }
      }
    })
    .component("player", {
      template: `
        <fieldset>
          $ctrl.box1={{$ctrl.box1}}<br>
          $ctrl.box2={{$ctrl.box2}}<br>
        </fieldset>
      `,
      controller: class player {
        constructor() {
          //console.log("player",this);
        }
        $onInit() {
          //console.log("pl.init", this)
          this.box1 = true;
          this.box2 = false;
        }
      },
    })
    <script src="//unpkg.com/angular@1.7.1/angular.js"></script>
    <body ng-app="app" ng-controller="ctrl as vm">
        Initial vm.pl = {{vm.initPL}}<br>
        Sample = {{vm.sample}}<br>
        <button ng-click="vm.getSample()">Get Sample</button>
        <br>
        <input type="checkbox" ng-model="vm.pl.box1" />
          Box1 pl.box1={{vm.pl.box1}}<br>
        <input type="checkbox" ng-model="vm.pl.box2" />
          Box2 pl.box2={{vm.pl.box2}}<br>
        <player ng-ref="vm.pl"></player>
      </body>
        2
  •  1
  •   georgeawg    6 年前

    父控制器初始化发生在播放器控制器初始化之前,因此 initPL 作为 undefined 在第一个 $onInit . 就我个人而言,我更希望在父控制器初始化时定义并加载应传递给嵌套组件的数据,而不是从其子控制器初始化时设置父控制器的初始数据。但如果需要,我们仍然可以使用绑定和回调在子控制器初始化时执行此操作。也许它看起来更像是一个肮脏的解决方案,但它可以在这种情况下工作,下面是代码:

    angular.module("app",[])
    .controller("ctrl", class ctrl {
      constructor() {
        console.log("construct")
      }
      $onInit() {
        console.log("onInit", this.pl);
        this.getSample = () => {
          this.sample = `[${this.pl.box1},${this.pl.box2}]`;
        }
        this.onPlayerInit = (pl) => {
          console.log("onPlayerInit", pl);
          this.initPL = pl || 'undefined';
          this.sample = `[${pl.box1},${pl.box2}]`;
        }
      }
    })
    .component("player", {
      bindings: {
          onInit: '&'
      },
      template: `
        <fieldset>
          $ctrl.box1={{$ctrl.box1}}<br>
          $ctrl.box2={{$ctrl.box2}}<br>
          <h3>Player</h3>
        </fieldset>
      `,
      controller: class player {
        constructor() {
          console.log("player",this);
        }
        $onInit() {
          console.log("pl.init", this)
          this.box1 = true;
          this.box2 = false;
          if (angular.isFunction( this.onInit() )) {
            this.onInit()(this);
          }
        }
      },
    })
    <script src="//unpkg.com/angular@1.7.1/angular.js"></script>
    <body ng-app="app" ng-controller="ctrl as vm">
        Initial vm.pl = {{vm.initPL}}<br>
        Sample = {{vm.sample}}<br>
        <button ng-click="vm.getSample()">Get Sample</button>
        <br>
        <input type="checkbox" ng-model="vm.pl.box1" />
          Box1 pl.box1={{vm.pl.box1}}<br>
        <input type="checkbox" ng-model="vm.pl.box2" />
          Box2 pl.box2={{vm.pl.box2}}<br>
        <br>
        <player ng-ref="vm.pl" on-init="vm.onPlayerInit"></player>
    </body>